mirror of
https://github.com/zitadel/zitadel.git
synced 2025-01-10 03:03:39 +00:00
Merge branch 'main' into one-db-pool
This commit is contained in:
commit
6091645362
@ -12,9 +12,11 @@ If you are using Zitadel, please consider adding yourself as a user with a quick
|
|||||||
| ----------------------- | -------------------------------------------------------------------- | ----------------------------------------------- |
|
| ----------------------- | -------------------------------------------------------------------- | ----------------------------------------------- |
|
||||||
| Zitadel | [@fforootd](https://github.com/fforootd) (and many more) | Zitadel Cloud makes heavy use of of Zitadel ;-) |
|
| Zitadel | [@fforootd](https://github.com/fforootd) (and many more) | Zitadel Cloud makes heavy use of of Zitadel ;-) |
|
||||||
| Rawkode Academy | [@RawkodeAcademy](https://github.com/RawkodeAcademy) | Rawkode Academy Platform & Zulip use Zitadel for all user and M2M authentication |
|
| Rawkode Academy | [@RawkodeAcademy](https://github.com/RawkodeAcademy) | Rawkode Academy Platform & Zulip use Zitadel for all user and M2M authentication |
|
||||||
|
| XPeditionist | [@XPeditionistTravel](https://github.com/XPeditionistTravel) | An innovative all-in-one travel solution use Zitadel as complete auth solution. |
|
||||||
| devOS: Sanity Edition | [@devOS-Sanity-Edition](https://github.com/devOS-Sanity-Edition) | Uses SSO Auth for every piece of our internal and external infrastructure |
|
| devOS: Sanity Edition | [@devOS-Sanity-Edition](https://github.com/devOS-Sanity-Edition) | Uses SSO Auth for every piece of our internal and external infrastructure |
|
||||||
| CNAP.tech | [@cnap-tech](https://github.com/cnap-tech) | Using Zitadel for authentication and authorization in cloud-native applications |
|
| CNAP.tech | [@cnap-tech](https://github.com/cnap-tech) | Using Zitadel for authentication and authorization in cloud-native applications |
|
||||||
| Minekube | [@minekube](https://github.com/minekube) | Leveraging Zitadel for secure user authentication in gaming infrastructure |
|
| Minekube | [@minekube](https://github.com/minekube) | Leveraging Zitadel for secure user authentication in gaming infrastructure |
|
||||||
|
| Dribdat | [@dribdat](https://github.com/dribdat) | Educating people about strong auth and resilient identity at hackathons |
|
||||||
| Micromate | [@sschoeb](https://github.com/sschoeb) | Using Zitadel for authentication and authorization for learners and managers in our digital learning assistant as well as in the Micromate manage platform |
|
| Micromate | [@sschoeb](https://github.com/sschoeb) | Using Zitadel for authentication and authorization for learners and managers in our digital learning assistant as well as in the Micromate manage platform |
|
||||||
| Smat.io | [@smatio](https://github.com/smatio) - [@lukasver](https://github.com/lukasver) | Zitadel for authentication in cloud applications while offering B2B portfolio management solutions for professional investors |
|
| Smat.io | [@smatio](https://github.com/smatio) - [@lukasver](https://github.com/lukasver) | Zitadel for authentication in cloud applications while offering B2B portfolio management solutions for professional investors |
|
||||||
|hirschengraben | [hirschengraben.io](hirschengraben.io) | Using Zitadel as IDP for a multitenant B2B dispatch app for bike messengers |
|
|hirschengraben | [hirschengraben.io](hirschengraben.io) | Using Zitadel as IDP for a multitenant B2B dispatch app for bike messengers |
|
||||||
|
@ -187,8 +187,11 @@ Caches:
|
|||||||
AutoPrune:
|
AutoPrune:
|
||||||
Interval: 1m
|
Interval: 1m
|
||||||
TimeOut: 5s
|
TimeOut: 5s
|
||||||
|
# Postgres connector uses the configured database (postgres or cockraochdb) as cache.
|
||||||
|
# It is suitable for deployments with multiple containers.
|
||||||
|
# The cache is enabled by default because it is the default cache states for IdP form callbacks
|
||||||
Postgres:
|
Postgres:
|
||||||
Enabled: false
|
Enabled: true
|
||||||
AutoPrune:
|
AutoPrune:
|
||||||
Interval: 15m
|
Interval: 15m
|
||||||
TimeOut: 30s
|
TimeOut: 30s
|
||||||
@ -300,7 +303,7 @@ Caches:
|
|||||||
# When connector is empty, this cache will be disabled.
|
# When connector is empty, this cache will be disabled.
|
||||||
Connector: ""
|
Connector: ""
|
||||||
MaxAge: 1h
|
MaxAge: 1h
|
||||||
LastUsage: 10m
|
LastUseAge: 10m
|
||||||
# Log enables cache-specific logging. Default to error log to stderr when omitted.
|
# Log enables cache-specific logging. Default to error log to stderr when omitted.
|
||||||
Log:
|
Log:
|
||||||
Level: error
|
Level: error
|
||||||
@ -311,7 +314,7 @@ Caches:
|
|||||||
Milestones:
|
Milestones:
|
||||||
Connector: ""
|
Connector: ""
|
||||||
MaxAge: 1h
|
MaxAge: 1h
|
||||||
LastUsage: 10m
|
LastUseAge: 10m
|
||||||
Log:
|
Log:
|
||||||
Level: error
|
Level: error
|
||||||
AddSource: true
|
AddSource: true
|
||||||
@ -321,7 +324,17 @@ Caches:
|
|||||||
Organization:
|
Organization:
|
||||||
Connector: ""
|
Connector: ""
|
||||||
MaxAge: 1h
|
MaxAge: 1h
|
||||||
LastUsage: 10m
|
LastUseAge: 10m
|
||||||
|
Log:
|
||||||
|
Level: error
|
||||||
|
AddSource: true
|
||||||
|
Formatter:
|
||||||
|
Format: text
|
||||||
|
# IdP callbacks using form POST cache, required for handling them securely and without possible too big request urls.
|
||||||
|
IdPFormCallbacks:
|
||||||
|
Connector: "postgres"
|
||||||
|
MaxAge: 1h
|
||||||
|
LastUseAge: 10m
|
||||||
Log:
|
Log:
|
||||||
Level: error
|
Level: error
|
||||||
AddSource: true
|
AddSource: true
|
||||||
@ -587,6 +600,9 @@ Console:
|
|||||||
# 168h is 7 days, one week
|
# 168h is 7 days, one week
|
||||||
SharedMaxAge: 168h # ZITADEL_CONSOLE_LONGCACHE_SHAREDMAXAGE
|
SharedMaxAge: 168h # ZITADEL_CONSOLE_LONGCACHE_SHAREDMAXAGE
|
||||||
InstanceManagementURL: "" # ZITADEL_CONSOLE_INSTANCEMANAGEMENTURL
|
InstanceManagementURL: "" # ZITADEL_CONSOLE_INSTANCEMANAGEMENTURL
|
||||||
|
PostHog:
|
||||||
|
URL: "" # ZITADEL_CONSOLE_POSTHOG_URL
|
||||||
|
Token: "" # ZITADEL_CONSOLE_POSTHOG_TOKEN
|
||||||
|
|
||||||
EncryptionKeys:
|
EncryptionKeys:
|
||||||
DomainVerification:
|
DomainVerification:
|
||||||
|
40
cmd/setup/43.go
Normal file
40
cmd/setup/43.go
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
package setup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"embed"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/zitadel/logging"
|
||||||
|
|
||||||
|
"github.com/zitadel/zitadel/internal/database"
|
||||||
|
"github.com/zitadel/zitadel/internal/eventstore"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
//go:embed 43/cockroach/*.sql
|
||||||
|
//go:embed 43/postgres/*.sql
|
||||||
|
createFieldsDomainIndex embed.FS
|
||||||
|
)
|
||||||
|
|
||||||
|
type CreateFieldsDomainIndex struct {
|
||||||
|
dbClient *database.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mig *CreateFieldsDomainIndex) Execute(ctx context.Context, _ eventstore.Event) error {
|
||||||
|
statements, err := readStatements(createFieldsDomainIndex, "43", mig.dbClient.Type())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, stmt := range statements {
|
||||||
|
logging.WithFields("file", stmt.file, "migration", mig.String()).Info("execute statement")
|
||||||
|
if _, err := mig.dbClient.ExecContext(ctx, stmt.query); err != nil {
|
||||||
|
return fmt.Errorf("%s %s: %w", mig.String(), stmt.file, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mig *CreateFieldsDomainIndex) String() string {
|
||||||
|
return "43_create_fields_domain_index"
|
||||||
|
}
|
3
cmd/setup/43/cockroach/43.sql
Normal file
3
cmd/setup/43/cockroach/43.sql
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
CREATE INDEX CONCURRENTLY IF NOT EXISTS fields_instance_domains_idx
|
||||||
|
ON eventstore.fields (object_id)
|
||||||
|
WHERE object_type = 'instance_domain' AND field_name = 'domain';
|
3
cmd/setup/43/postgres/43.sql
Normal file
3
cmd/setup/43/postgres/43.sql
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
CREATE INDEX CONCURRENTLY IF NOT EXISTS fields_instance_domains_idx
|
||||||
|
ON eventstore.fields (object_id) INCLUDE (instance_id)
|
||||||
|
WHERE object_type = 'instance_domain' AND field_name = 'domain';
|
@ -128,6 +128,7 @@ type Steps struct {
|
|||||||
s38BackChannelLogoutNotificationStart *BackChannelLogoutNotificationStart
|
s38BackChannelLogoutNotificationStart *BackChannelLogoutNotificationStart
|
||||||
s40InitPushFunc *InitPushFunc
|
s40InitPushFunc *InitPushFunc
|
||||||
s42Apps7OIDCConfigsLoginVersion *Apps7OIDCConfigsLoginVersion
|
s42Apps7OIDCConfigsLoginVersion *Apps7OIDCConfigsLoginVersion
|
||||||
|
s43CreateFieldsDomainIndex *CreateFieldsDomainIndex
|
||||||
}
|
}
|
||||||
|
|
||||||
func MustNewSteps(v *viper.Viper) *Steps {
|
func MustNewSteps(v *viper.Viper) *Steps {
|
||||||
|
@ -166,6 +166,7 @@ func Setup(ctx context.Context, config *Config, steps *Steps, masterKey string)
|
|||||||
steps.s38BackChannelLogoutNotificationStart = &BackChannelLogoutNotificationStart{dbClient: dbClient, esClient: eventstoreClient}
|
steps.s38BackChannelLogoutNotificationStart = &BackChannelLogoutNotificationStart{dbClient: dbClient, esClient: eventstoreClient}
|
||||||
steps.s40InitPushFunc = &InitPushFunc{dbClient: dbClient}
|
steps.s40InitPushFunc = &InitPushFunc{dbClient: dbClient}
|
||||||
steps.s42Apps7OIDCConfigsLoginVersion = &Apps7OIDCConfigsLoginVersion{dbClient: dbClient}
|
steps.s42Apps7OIDCConfigsLoginVersion = &Apps7OIDCConfigsLoginVersion{dbClient: dbClient}
|
||||||
|
steps.s43CreateFieldsDomainIndex = &CreateFieldsDomainIndex{dbClient: dbClient}
|
||||||
|
|
||||||
err = projection.Create(ctx, dbClient, eventstoreClient, config.Projections, nil, nil, nil)
|
err = projection.Create(ctx, dbClient, eventstoreClient, config.Projections, nil, nil, nil)
|
||||||
logging.OnError(err).Fatal("unable to start projections")
|
logging.OnError(err).Fatal("unable to start projections")
|
||||||
@ -237,6 +238,7 @@ func Setup(ctx context.Context, config *Config, steps *Steps, masterKey string)
|
|||||||
steps.s33SMSConfigs3TwilioAddVerifyServiceSid,
|
steps.s33SMSConfigs3TwilioAddVerifyServiceSid,
|
||||||
steps.s37Apps7OIDConfigsBackChannelLogoutURI,
|
steps.s37Apps7OIDConfigsBackChannelLogoutURI,
|
||||||
steps.s42Apps7OIDCConfigsLoginVersion,
|
steps.s42Apps7OIDCConfigsLoginVersion,
|
||||||
|
steps.s43CreateFieldsDomainIndex,
|
||||||
} {
|
} {
|
||||||
mustExecuteMigration(ctx, eventstoreClient, step, "migration failed")
|
mustExecuteMigration(ctx, eventstoreClient, step, "migration failed")
|
||||||
}
|
}
|
||||||
|
@ -308,6 +308,7 @@ func startZitadel(ctx context.Context, config *Config, masterKey string, server
|
|||||||
authZRepo,
|
authZRepo,
|
||||||
keys,
|
keys,
|
||||||
permissionCheck,
|
permissionCheck,
|
||||||
|
cacheConnectors,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -352,6 +353,7 @@ func startAPIs(
|
|||||||
authZRepo authz_repo.Repository,
|
authZRepo authz_repo.Repository,
|
||||||
keys *encryption.EncryptionKeys,
|
keys *encryption.EncryptionKeys,
|
||||||
permissionCheck domain.PermissionCheck,
|
permissionCheck domain.PermissionCheck,
|
||||||
|
cacheConnectors connector.Connectors,
|
||||||
) (*api.API, error) {
|
) (*api.API, error) {
|
||||||
repo := struct {
|
repo := struct {
|
||||||
authz_repo.Repository
|
authz_repo.Repository
|
||||||
@ -533,6 +535,7 @@ func startAPIs(
|
|||||||
keys.User,
|
keys.User,
|
||||||
keys.IDPConfig,
|
keys.IDPConfig,
|
||||||
keys.CSRFCookieKey,
|
keys.CSRFCookieKey,
|
||||||
|
cacheConnectors,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to start login: %w", err)
|
return nil, fmt.Errorf("unable to start login: %w", err)
|
||||||
|
@ -43,11 +43,10 @@ dotnet add package Zitadel.Api
|
|||||||
### Create example client
|
### Create example client
|
||||||
|
|
||||||
Change the program.cs file to the content below. This will create a client for the management api and call its `GetMyUsers` function.
|
Change the program.cs file to the content below. This will create a client for the management api and call its `GetMyUsers` function.
|
||||||
The SDK will make sure you will have access to the API by retrieving a Bearer Token using JWT Profile with the provided scopes (`openid` and `urn:zitadel:iam:org:project:id:{projectID}:aud`).
|
The SDK will make sure you will have access to the API by retrieving a Bearer Token using JWT Profile with the provided scopes (`openid` and `urn:zitadel:iam:org:project:id:zitadel:aud`).
|
||||||
|
|
||||||
Make sure to fill the const `apiUrl`, `apiProject` and `personalAccessToken` with your own instance data. The used vars below are from a test instance, to show you how it should look.
|
Make sure to fill the const `apiUrl`, and `personalAccessToken` with your own instance data. The used vars below are from a test instance, to show you how it should look.
|
||||||
The apiURL is the domain of your instance you can find it on the instance detail in the Customer Portal or in the Console
|
The apiURL is the domain of your instance you can find it on the instance detail in the Customer Portal or in the Console
|
||||||
The apiProject you will find in the ZITADEL project in the first organization of your instance.
|
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
// This file contains two examples:
|
// This file contains two examples:
|
||||||
@ -66,7 +65,8 @@ var client = Clients.AuthService(new(apiUrl, ITokenProvider.Static(personalAcces
|
|||||||
var result = await client.GetMyUserAsync(new());
|
var result = await client.GetMyUserAsync(new());
|
||||||
Console.WriteLine($"User: {result.User}");
|
Console.WriteLine($"User: {result.User}");
|
||||||
|
|
||||||
const string apiProject = "170078979166961921";
|
// This adds the urn:zitadel:iam:org:project:id:zitadel:aud scope to the authorization request, enabling access to ZITADEL APIs.
|
||||||
|
const string apiProject = "zitadel";
|
||||||
var serviceAccount = ServiceAccount.LoadFromJsonString(
|
var serviceAccount = ServiceAccount.LoadFromJsonString(
|
||||||
@"
|
@"
|
||||||
{
|
{
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
version: '3.8'
|
|
||||||
services:
|
services:
|
||||||
|
|
||||||
traefik:
|
traefik:
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
version: "3.8"
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
zitadel:
|
zitadel:
|
||||||
restart: "always"
|
restart: "always"
|
||||||
|
@ -24,7 +24,7 @@ export const Description = ({mode, link}) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const Commands = ({mode, name, lower, configfilename}) => {
|
export const Commands = ({mode, name, lower, configfilename}) => {
|
||||||
let genCert = '# Generate a self signed certificate and key.\nopenssl req -x509 -batch -subj "/CN=127.0.0.1.sslip.io/O=ZITADEL Demo" -nodes -newkey rsa:2048 -keyout ./selfsigned.key -out ./selfsigned.crt\n\n';
|
let genCert = '# Generate a self signed certificate and key.\nopenssl req -x509 -batch -subj "/CN=127.0.0.1.sslip.io/O=ZITADEL Demo" -nodes -newkey rsa:2048 -keyout ./selfsigned.key -out ./selfsigned.crt 2>/dev/null\n\n';
|
||||||
let connPort = "443"
|
let connPort = "443"
|
||||||
let connInsecureFlag = "--insecure "
|
let connInsecureFlag = "--insecure "
|
||||||
let connScheme = "https"
|
let connScheme = "https"
|
||||||
@ -42,16 +42,16 @@ export const Commands = ({mode, name, lower, configfilename}) => {
|
|||||||
<CodeBlock language="bash">
|
<CodeBlock language="bash">
|
||||||
{'# Download the configuration files.'}{'\n'}
|
{'# Download the configuration files.'}{'\n'}
|
||||||
{'export ZITADEL_CONFIG_FILES=https://raw.githubusercontent.com/zitadel/zitadel/main/docs/docs/self-hosting/manage/reverseproxy\n'}
|
{'export ZITADEL_CONFIG_FILES=https://raw.githubusercontent.com/zitadel/zitadel/main/docs/docs/self-hosting/manage/reverseproxy\n'}
|
||||||
{`wget $\{ZITADEL_CONFIG_FILES\}/docker-compose.yaml -O docker-compose-base.yaml`}{'\n'}
|
{'wget $\{ZITADEL_CONFIG_FILES\}/docker-compose.yaml -O docker-compose-base.yaml --quiet \n'}
|
||||||
{'wget $\{ZITADEL_CONFIG_FILES\}/'}{lower}{'/docker-compose.yaml -O docker-compose-'}{lower}{'.yaml'}{'\n'}
|
{'wget $\{ZITADEL_CONFIG_FILES\}/'}{lower}{'/docker-compose.yaml -O docker-compose-'}{lower}{'.yaml --quiet \n'}
|
||||||
{'wget $\{ZITADEL_CONFIG_FILES\}/'}{lower}{'/'}{configfilename}{' -O '}{configfilename}{'\n'}
|
{'wget $\{ZITADEL_CONFIG_FILES\}/'}{lower}{'/'}{configfilename}{' -O '}{configfilename}{' --quiet \n'}
|
||||||
{'\n'}
|
{'\n'}
|
||||||
{genCert}
|
{genCert}
|
||||||
{'# Run the database, ZITADEL and '}{name}{'.'}{'\n'}
|
{'# Run the database, ZITADEL and '}{name}{'.'}{'\n'}
|
||||||
{'docker compose --file docker-compose-base.yaml --file docker-compose-'}{lower}{'.yaml up --detach proxy-'}{mode}{'-tls'}{'\n'}
|
{'docker compose --file docker-compose-base.yaml --file docker-compose-'}{lower}{'.yaml up --detach --wait db zitadel-init zitadel-'}{mode}{'-tls proxy-'}{mode}{'-tls'}{'\n'}
|
||||||
{'\n'}
|
{'\n'}
|
||||||
{'# Test that gRPC and HTTP APIs work. Empty brackets like {} means success.\n'}
|
{'# Test that gRPC and HTTP APIs work. Empty brackets like {} means success.\n'}
|
||||||
{'sleep 3\n'}
|
{'# Make sure you have the grpcurl cli installed on your machine https://github.com/fullstorydev/grpcurl?tab=readme-ov-file#installation\n'}
|
||||||
{'grpcurl '}{connInsecureFlag}{grpcPlainTextFlag}{'127.0.0.1.sslip.io:'}{connPort}{' zitadel.admin.v1.AdminService/Healthz\n'}
|
{'grpcurl '}{connInsecureFlag}{grpcPlainTextFlag}{'127.0.0.1.sslip.io:'}{connPort}{' zitadel.admin.v1.AdminService/Healthz\n'}
|
||||||
{'curl '}{connInsecureFlag}{connScheme}{'://127.0.0.1.sslip.io:'}{connPort}{'/admin/v1/healthz\n'}
|
{'curl '}{connInsecureFlag}{connScheme}{'://127.0.0.1.sslip.io:'}{connPort}{'/admin/v1/healthz\n'}
|
||||||
</CodeBlock>
|
</CodeBlock>
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
version: '3.8'
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
|
|
||||||
proxy-disabled-tls:
|
proxy-disabled-tls:
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
version: '3.8'
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
|
|
||||||
zitadel-disabled-tls:
|
zitadel-disabled-tls:
|
||||||
@ -17,7 +15,7 @@ services:
|
|||||||
ZITADEL_DATABASE_POSTGRES_USER_USERNAME: zitadel_user
|
ZITADEL_DATABASE_POSTGRES_USER_USERNAME: zitadel_user
|
||||||
ZITADEL_DATABASE_POSTGRES_USER_PASSWORD: zitadel_pw
|
ZITADEL_DATABASE_POSTGRES_USER_PASSWORD: zitadel_pw
|
||||||
ZITADEL_DATABASE_POSTGRES_USER_SSL_MODE: disable
|
ZITADEL_DATABASE_POSTGRES_USER_SSL_MODE: disable
|
||||||
ZITADEL_DATABASE_POSTGRES_ADMIN_USERNAME: root
|
ZITADEL_DATABASE_POSTGRES_ADMIN_USERNAME: postgres
|
||||||
ZITADEL_DATABASE_POSTGRES_ADMIN_PASSWORD: postgres
|
ZITADEL_DATABASE_POSTGRES_ADMIN_PASSWORD: postgres
|
||||||
ZITADEL_DATABASE_POSTGRES_ADMIN_SSL_MODE: disable
|
ZITADEL_DATABASE_POSTGRES_ADMIN_SSL_MODE: disable
|
||||||
networks:
|
networks:
|
||||||
@ -43,16 +41,16 @@ services:
|
|||||||
ZITADEL_DATABASE_POSTGRES_USER_USERNAME: zitadel_user
|
ZITADEL_DATABASE_POSTGRES_USER_USERNAME: zitadel_user
|
||||||
ZITADEL_DATABASE_POSTGRES_USER_PASSWORD: zitadel_pw
|
ZITADEL_DATABASE_POSTGRES_USER_PASSWORD: zitadel_pw
|
||||||
ZITADEL_DATABASE_POSTGRES_USER_SSL_MODE: disable
|
ZITADEL_DATABASE_POSTGRES_USER_SSL_MODE: disable
|
||||||
ZITADEL_DATABASE_POSTGRES_ADMIN_USERNAME: root
|
ZITADEL_DATABASE_POSTGRES_ADMIN_USERNAME: postgres
|
||||||
ZITADEL_DATABASE_POSTGRES_ADMIN_PASSWORD: postgres
|
ZITADEL_DATABASE_POSTGRES_ADMIN_PASSWORD: postgres
|
||||||
ZITADEL_DATABASE_POSTGRES_ADMIN_SSL_MODE: disable
|
ZITADEL_DATABASE_POSTGRES_ADMIN_SSL_MODE: disable
|
||||||
networks:
|
networks:
|
||||||
- 'zitadel'
|
- 'zitadel'
|
||||||
depends_on:
|
depends_on:
|
||||||
zitadel-init:
|
|
||||||
condition: 'service_completed_successfully'
|
|
||||||
db:
|
db:
|
||||||
condition: 'service_healthy'
|
condition: 'service_healthy'
|
||||||
|
zitadel-init:
|
||||||
|
condition: 'service_completed_successfully'
|
||||||
|
|
||||||
zitadel-enabled-tls:
|
zitadel-enabled-tls:
|
||||||
extends:
|
extends:
|
||||||
@ -71,7 +69,7 @@ services:
|
|||||||
ZITADEL_DATABASE_POSTGRES_USER_USERNAME: zitadel_user
|
ZITADEL_DATABASE_POSTGRES_USER_USERNAME: zitadel_user
|
||||||
ZITADEL_DATABASE_POSTGRES_USER_PASSWORD: zitadel_pw
|
ZITADEL_DATABASE_POSTGRES_USER_PASSWORD: zitadel_pw
|
||||||
ZITADEL_DATABASE_POSTGRES_USER_SSL_MODE: disable
|
ZITADEL_DATABASE_POSTGRES_USER_SSL_MODE: disable
|
||||||
ZITADEL_DATABASE_POSTGRES_ADMIN_USERNAME: root
|
ZITADEL_DATABASE_POSTGRES_ADMIN_USERNAME: postgres
|
||||||
ZITADEL_DATABASE_POSTGRES_ADMIN_PASSWORD: postgres
|
ZITADEL_DATABASE_POSTGRES_ADMIN_PASSWORD: postgres
|
||||||
ZITADEL_DATABASE_POSTGRES_ADMIN_SSL_MODE: disable
|
ZITADEL_DATABASE_POSTGRES_ADMIN_SSL_MODE: disable
|
||||||
volumes:
|
volumes:
|
||||||
@ -109,7 +107,7 @@ services:
|
|||||||
ZITADEL_DATABASE_POSTGRES_USER_USERNAME: zitadel_user
|
ZITADEL_DATABASE_POSTGRES_USER_USERNAME: zitadel_user
|
||||||
ZITADEL_DATABASE_POSTGRES_USER_PASSWORD: zitadel_pw
|
ZITADEL_DATABASE_POSTGRES_USER_PASSWORD: zitadel_pw
|
||||||
ZITADEL_DATABASE_POSTGRES_USER_SSL_MODE: disable
|
ZITADEL_DATABASE_POSTGRES_USER_SSL_MODE: disable
|
||||||
ZITADEL_DATABASE_POSTGRES_ADMIN_USERNAME: root
|
ZITADEL_DATABASE_POSTGRES_ADMIN_USERNAME: postgres
|
||||||
ZITADEL_DATABASE_POSTGRES_ADMIN_PASSWORD: postgres
|
ZITADEL_DATABASE_POSTGRES_ADMIN_PASSWORD: postgres
|
||||||
ZITADEL_DATABASE_POSTGRES_ADMIN_SSL_MODE: disable
|
ZITADEL_DATABASE_POSTGRES_ADMIN_SSL_MODE: disable
|
||||||
networks:
|
networks:
|
||||||
@ -125,10 +123,9 @@ services:
|
|||||||
restart: 'always'
|
restart: 'always'
|
||||||
image: postgres:16-alpine
|
image: postgres:16-alpine
|
||||||
environment:
|
environment:
|
||||||
PGUSER: root
|
|
||||||
POSTGRES_PASSWORD: postgres
|
POSTGRES_PASSWORD: postgres
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD-SHELL", "pg_isready", "-d", "zitadel", "-U", "postgres"]
|
test: ["CMD-SHELL", "pg_isready"]
|
||||||
interval: 5s
|
interval: 5s
|
||||||
timeout: 60s
|
timeout: 60s
|
||||||
retries: 10
|
retries: 10
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
version: '3.8'
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
|
|
||||||
proxy-disabled-tls:
|
proxy-disabled-tls:
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
version: '3.8'
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
|
|
||||||
proxy-disabled-tls:
|
proxy-disabled-tls:
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
version: '3.8'
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
|
|
||||||
proxy-disabled-tls:
|
proxy-disabled-tls:
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
version: '3.8'
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
|
|
||||||
db:
|
db:
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
version: '3.8'
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
zitadel:
|
zitadel:
|
||||||
user: '$UID'
|
user: '$UID'
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
version: '3.8'
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
zitadel:
|
zitadel:
|
||||||
extends:
|
extends:
|
||||||
|
@ -35,7 +35,7 @@ func TestServer_ListIAMMemberRoles(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestServer_ListIAMMembers(t *testing.T) {
|
func TestServer_ListIAMMembers(t *testing.T) {
|
||||||
user := Instance.CreateHumanUserVerified(AdminCTX, Instance.DefaultOrg.Id, gofakeit.Email())
|
user := Instance.CreateHumanUserVerified(AdminCTX, Instance.DefaultOrg.Id, gofakeit.Email(), gofakeit.Phone())
|
||||||
_, err := Client.AddIAMMember(AdminCTX, &admin_pb.AddIAMMemberRequest{
|
_, err := Client.AddIAMMember(AdminCTX, &admin_pb.AddIAMMemberRequest{
|
||||||
UserId: user.GetUserId(),
|
UserId: user.GetUserId(),
|
||||||
Roles: iamRoles,
|
Roles: iamRoles,
|
||||||
@ -116,7 +116,7 @@ func TestServer_ListIAMMembers(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestServer_AddIAMMember(t *testing.T) {
|
func TestServer_AddIAMMember(t *testing.T) {
|
||||||
user := Instance.CreateHumanUserVerified(AdminCTX, Instance.DefaultOrg.Id, gofakeit.Email())
|
user := Instance.CreateHumanUserVerified(AdminCTX, Instance.DefaultOrg.Id, gofakeit.Email(), gofakeit.Phone())
|
||||||
type args struct {
|
type args struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
req *admin_pb.AddIAMMemberRequest
|
req *admin_pb.AddIAMMemberRequest
|
||||||
@ -190,7 +190,7 @@ func TestServer_AddIAMMember(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestServer_UpdateIAMMember(t *testing.T) {
|
func TestServer_UpdateIAMMember(t *testing.T) {
|
||||||
user := Instance.CreateHumanUserVerified(AdminCTX, Instance.DefaultOrg.Id, gofakeit.Email())
|
user := Instance.CreateHumanUserVerified(AdminCTX, Instance.DefaultOrg.Id, gofakeit.Email(), gofakeit.Phone())
|
||||||
_, err := Client.AddIAMMember(AdminCTX, &admin_pb.AddIAMMemberRequest{
|
_, err := Client.AddIAMMember(AdminCTX, &admin_pb.AddIAMMemberRequest{
|
||||||
UserId: user.GetUserId(),
|
UserId: user.GetUserId(),
|
||||||
Roles: []string{"IAM_OWNER"},
|
Roles: []string{"IAM_OWNER"},
|
||||||
@ -271,7 +271,7 @@ func TestServer_UpdateIAMMember(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestServer_RemoveIAMMember(t *testing.T) {
|
func TestServer_RemoveIAMMember(t *testing.T) {
|
||||||
user := Instance.CreateHumanUserVerified(AdminCTX, Instance.DefaultOrg.Id, gofakeit.Email())
|
user := Instance.CreateHumanUserVerified(AdminCTX, Instance.DefaultOrg.Id, gofakeit.Email(), gofakeit.Phone())
|
||||||
_, err := Client.AddIAMMember(AdminCTX, &admin_pb.AddIAMMemberRequest{
|
_, err := Client.AddIAMMember(AdminCTX, &admin_pb.AddIAMMemberRequest{
|
||||||
UserId: user.GetUserId(),
|
UserId: user.GetUserId(),
|
||||||
Roles: []string{"IAM_OWNER"},
|
Roles: []string{"IAM_OWNER"},
|
||||||
|
@ -39,7 +39,7 @@ func TestServer_ListOrgMemberRoles(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestServer_ListOrgMembers(t *testing.T) {
|
func TestServer_ListOrgMembers(t *testing.T) {
|
||||||
user := Instance.CreateHumanUserVerified(OrgCTX, Instance.DefaultOrg.Id, gofakeit.Email())
|
user := Instance.CreateHumanUserVerified(OrgCTX, Instance.DefaultOrg.Id, gofakeit.Email(), gofakeit.Phone())
|
||||||
_, err := Client.AddOrgMember(OrgCTX, &mgmt_pb.AddOrgMemberRequest{
|
_, err := Client.AddOrgMember(OrgCTX, &mgmt_pb.AddOrgMemberRequest{
|
||||||
UserId: user.GetUserId(),
|
UserId: user.GetUserId(),
|
||||||
Roles: iamRoles[1:],
|
Roles: iamRoles[1:],
|
||||||
@ -120,7 +120,7 @@ func TestServer_ListOrgMembers(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestServer_AddOrgMember(t *testing.T) {
|
func TestServer_AddOrgMember(t *testing.T) {
|
||||||
user := Instance.CreateHumanUserVerified(OrgCTX, Instance.DefaultOrg.Id, gofakeit.Email())
|
user := Instance.CreateHumanUserVerified(OrgCTX, Instance.DefaultOrg.Id, gofakeit.Email(), gofakeit.Phone())
|
||||||
type args struct {
|
type args struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
req *mgmt_pb.AddOrgMemberRequest
|
req *mgmt_pb.AddOrgMemberRequest
|
||||||
@ -194,7 +194,7 @@ func TestServer_AddOrgMember(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestServer_UpdateOrgMember(t *testing.T) {
|
func TestServer_UpdateOrgMember(t *testing.T) {
|
||||||
user := Instance.CreateHumanUserVerified(OrgCTX, Instance.DefaultOrg.Id, gofakeit.Email())
|
user := Instance.CreateHumanUserVerified(OrgCTX, Instance.DefaultOrg.Id, gofakeit.Email(), gofakeit.Phone())
|
||||||
_, err := Client.AddOrgMember(OrgCTX, &mgmt_pb.AddOrgMemberRequest{
|
_, err := Client.AddOrgMember(OrgCTX, &mgmt_pb.AddOrgMemberRequest{
|
||||||
UserId: user.GetUserId(),
|
UserId: user.GetUserId(),
|
||||||
Roles: []string{"ORG_OWNER"},
|
Roles: []string{"ORG_OWNER"},
|
||||||
@ -275,7 +275,7 @@ func TestServer_UpdateOrgMember(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestServer_RemoveIAMMember(t *testing.T) {
|
func TestServer_RemoveIAMMember(t *testing.T) {
|
||||||
user := Instance.CreateHumanUserVerified(OrgCTX, Instance.DefaultOrg.Id, gofakeit.Email())
|
user := Instance.CreateHumanUserVerified(OrgCTX, Instance.DefaultOrg.Id, gofakeit.Email(), gofakeit.Phone())
|
||||||
_, err := Client.AddOrgMember(OrgCTX, &mgmt_pb.AddOrgMemberRequest{
|
_, err := Client.AddOrgMember(OrgCTX, &mgmt_pb.AddOrgMemberRequest{
|
||||||
UserId: user.GetUserId(),
|
UserId: user.GetUserId(),
|
||||||
Roles: []string{"ORG_OWNER"},
|
Roles: []string{"ORG_OWNER"},
|
||||||
|
@ -9,6 +9,7 @@ import (
|
|||||||
"github.com/zitadel/zitadel/internal/domain"
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
"github.com/zitadel/zitadel/internal/query"
|
"github.com/zitadel/zitadel/internal/query"
|
||||||
"github.com/zitadel/zitadel/pkg/grpc/object/v2"
|
"github.com/zitadel/zitadel/pkg/grpc/object/v2"
|
||||||
|
user_pb "github.com/zitadel/zitadel/pkg/grpc/user/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
func DomainToDetailsPb(objectDetail *domain.ObjectDetails) *object.Details {
|
func DomainToDetailsPb(objectDetail *domain.ObjectDetails) *object.Details {
|
||||||
@ -70,3 +71,105 @@ func TextMethodToQuery(method object.TextQueryMethod) query.TextComparison {
|
|||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func AuthMethodsToPb(mfas *query.AuthMethods) []*user_pb.AuthFactor {
|
||||||
|
factors := make([]*user_pb.AuthFactor, len(mfas.AuthMethods))
|
||||||
|
for i, mfa := range mfas.AuthMethods {
|
||||||
|
factors[i] = AuthMethodToPb(mfa)
|
||||||
|
}
|
||||||
|
return factors
|
||||||
|
}
|
||||||
|
|
||||||
|
func AuthMethodToPb(mfa *query.AuthMethod) *user_pb.AuthFactor {
|
||||||
|
factor := &user_pb.AuthFactor{
|
||||||
|
State: MFAStateToPb(mfa.State),
|
||||||
|
}
|
||||||
|
switch mfa.Type {
|
||||||
|
case domain.UserAuthMethodTypeTOTP:
|
||||||
|
factor.Type = &user_pb.AuthFactor_Otp{
|
||||||
|
Otp: &user_pb.AuthFactorOTP{},
|
||||||
|
}
|
||||||
|
case domain.UserAuthMethodTypeU2F:
|
||||||
|
factor.Type = &user_pb.AuthFactor_U2F{
|
||||||
|
U2F: &user_pb.AuthFactorU2F{
|
||||||
|
Id: mfa.TokenID,
|
||||||
|
Name: mfa.Name,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
case domain.UserAuthMethodTypeOTPSMS:
|
||||||
|
factor.Type = &user_pb.AuthFactor_OtpSms{
|
||||||
|
OtpSms: &user_pb.AuthFactorOTPSMS{},
|
||||||
|
}
|
||||||
|
case domain.UserAuthMethodTypeOTPEmail:
|
||||||
|
factor.Type = &user_pb.AuthFactor_OtpEmail{
|
||||||
|
OtpEmail: &user_pb.AuthFactorOTPEmail{},
|
||||||
|
}
|
||||||
|
case domain.UserAuthMethodTypeUnspecified:
|
||||||
|
case domain.UserAuthMethodTypePasswordless:
|
||||||
|
case domain.UserAuthMethodTypePassword:
|
||||||
|
case domain.UserAuthMethodTypeIDP:
|
||||||
|
case domain.UserAuthMethodTypeOTP:
|
||||||
|
case domain.UserAuthMethodTypePrivateKey:
|
||||||
|
}
|
||||||
|
return factor
|
||||||
|
}
|
||||||
|
|
||||||
|
func AuthFactorsToPb(authFactors []user_pb.AuthFactors) []domain.UserAuthMethodType {
|
||||||
|
factors := make([]domain.UserAuthMethodType, len(authFactors))
|
||||||
|
for i, authFactor := range authFactors {
|
||||||
|
factors[i] = AuthFactorToPb(authFactor)
|
||||||
|
}
|
||||||
|
return factors
|
||||||
|
}
|
||||||
|
|
||||||
|
func AuthFactorToPb(authFactor user_pb.AuthFactors) domain.UserAuthMethodType {
|
||||||
|
switch authFactor {
|
||||||
|
case user_pb.AuthFactors_OTP:
|
||||||
|
return domain.UserAuthMethodTypeTOTP
|
||||||
|
case user_pb.AuthFactors_OTP_SMS:
|
||||||
|
return domain.UserAuthMethodTypeOTPSMS
|
||||||
|
case user_pb.AuthFactors_OTP_EMAIL:
|
||||||
|
return domain.UserAuthMethodTypeOTPEmail
|
||||||
|
case user_pb.AuthFactors_U2F:
|
||||||
|
return domain.UserAuthMethodTypeU2F
|
||||||
|
default:
|
||||||
|
return domain.UserAuthMethodTypeUnspecified
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func AuthFactorStatesToPb(authFactorStates []user_pb.AuthFactorState) []domain.MFAState {
|
||||||
|
factorStates := make([]domain.MFAState, len(authFactorStates))
|
||||||
|
for i, authFactorState := range authFactorStates {
|
||||||
|
factorStates[i] = AuthFactorStateToPb(authFactorState)
|
||||||
|
}
|
||||||
|
return factorStates
|
||||||
|
}
|
||||||
|
|
||||||
|
func AuthFactorStateToPb(authFactorState user_pb.AuthFactorState) domain.MFAState {
|
||||||
|
switch authFactorState {
|
||||||
|
case user_pb.AuthFactorState_AUTH_FACTOR_STATE_UNSPECIFIED:
|
||||||
|
return domain.MFAStateUnspecified
|
||||||
|
case user_pb.AuthFactorState_AUTH_FACTOR_STATE_NOT_READY:
|
||||||
|
return domain.MFAStateNotReady
|
||||||
|
case user_pb.AuthFactorState_AUTH_FACTOR_STATE_READY:
|
||||||
|
return domain.MFAStateReady
|
||||||
|
case user_pb.AuthFactorState_AUTH_FACTOR_STATE_REMOVED:
|
||||||
|
return domain.MFAStateRemoved
|
||||||
|
default:
|
||||||
|
return domain.MFAStateUnspecified
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func MFAStateToPb(state domain.MFAState) user_pb.AuthFactorState {
|
||||||
|
switch state {
|
||||||
|
case domain.MFAStateNotReady:
|
||||||
|
return user_pb.AuthFactorState_AUTH_FACTOR_STATE_NOT_READY
|
||||||
|
case domain.MFAStateReady:
|
||||||
|
return user_pb.AuthFactorState_AUTH_FACTOR_STATE_READY
|
||||||
|
case domain.MFAStateUnspecified, domain.MFAStateRemoved:
|
||||||
|
// Handle all remaining cases so the linter succeeds
|
||||||
|
return user_pb.AuthFactorState_AUTH_FACTOR_STATE_UNSPECIFIED
|
||||||
|
default:
|
||||||
|
return user_pb.AuthFactorState_AUTH_FACTOR_STATE_UNSPECIFIED
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -67,6 +67,33 @@ func (s *Server) ResendEmailCode(ctx context.Context, req *user.ResendEmailCodeR
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Server) SendEmailCode(ctx context.Context, req *user.SendEmailCodeRequest) (resp *user.SendEmailCodeResponse, err error) {
|
||||||
|
var email *domain.Email
|
||||||
|
|
||||||
|
switch v := req.GetVerification().(type) {
|
||||||
|
case *user.SendEmailCodeRequest_SendCode:
|
||||||
|
email, err = s.command.SendUserEmailCodeURLTemplate(ctx, req.GetUserId(), s.userCodeAlg, v.SendCode.GetUrlTemplate())
|
||||||
|
case *user.SendEmailCodeRequest_ReturnCode:
|
||||||
|
email, err = s.command.SendUserEmailReturnCode(ctx, req.GetUserId(), s.userCodeAlg)
|
||||||
|
case nil:
|
||||||
|
email, err = s.command.SendUserEmailCode(ctx, req.GetUserId(), s.userCodeAlg)
|
||||||
|
default:
|
||||||
|
err = zerrors.ThrowUnimplementedf(nil, "USERv2-faj0l0nj5x", "verification oneOf %T in method SendEmailCode not implemented", v)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &user.SendEmailCodeResponse{
|
||||||
|
Details: &object.Details{
|
||||||
|
Sequence: email.Sequence,
|
||||||
|
ChangeDate: timestamppb.New(email.ChangeDate),
|
||||||
|
ResourceOwner: email.ResourceOwner,
|
||||||
|
},
|
||||||
|
VerificationCode: email.PlainCode,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Server) VerifyEmail(ctx context.Context, req *user.VerifyEmailRequest) (*user.VerifyEmailResponse, error) {
|
func (s *Server) VerifyEmail(ctx context.Context, req *user.VerifyEmailRequest) (*user.VerifyEmailResponse, error) {
|
||||||
details, err := s.command.VerifyUserEmail(ctx,
|
details, err := s.command.VerifyUserEmail(ctx,
|
||||||
req.GetUserId(),
|
req.GetUserId(),
|
||||||
|
@ -147,7 +147,7 @@ func TestServer_SetEmail(t *testing.T) {
|
|||||||
|
|
||||||
func TestServer_ResendEmailCode(t *testing.T) {
|
func TestServer_ResendEmailCode(t *testing.T) {
|
||||||
userID := Instance.CreateHumanUser(CTX).GetUserId()
|
userID := Instance.CreateHumanUser(CTX).GetUserId()
|
||||||
verifiedUserID := Instance.CreateHumanUserVerified(CTX, Instance.DefaultOrg.Id, gofakeit.Email()).GetUserId()
|
verifiedUserID := Instance.CreateHumanUserVerified(CTX, Instance.DefaultOrg.Id, gofakeit.Email(), gofakeit.Phone()).GetUserId()
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@ -249,6 +249,116 @@ func TestServer_ResendEmailCode(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestServer_SendEmailCode(t *testing.T) {
|
||||||
|
userID := Instance.CreateHumanUser(CTX).GetUserId()
|
||||||
|
verifiedUserID := Instance.CreateHumanUserVerified(CTX, Instance.DefaultOrg.Id, gofakeit.Email(), gofakeit.Phone()).GetUserId()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
req *user.SendEmailCodeRequest
|
||||||
|
want *user.SendEmailCodeResponse
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "user not existing",
|
||||||
|
req: &user.SendEmailCodeRequest{
|
||||||
|
UserId: "xxx",
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "user no code",
|
||||||
|
req: &user.SendEmailCodeRequest{
|
||||||
|
UserId: verifiedUserID,
|
||||||
|
},
|
||||||
|
want: &user.SendEmailCodeResponse{
|
||||||
|
Details: &object.Details{
|
||||||
|
Sequence: 1,
|
||||||
|
ChangeDate: timestamppb.Now(),
|
||||||
|
ResourceOwner: Instance.DefaultOrg.Id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "resend",
|
||||||
|
req: &user.SendEmailCodeRequest{
|
||||||
|
UserId: userID,
|
||||||
|
},
|
||||||
|
want: &user.SendEmailCodeResponse{
|
||||||
|
Details: &object.Details{
|
||||||
|
Sequence: 1,
|
||||||
|
ChangeDate: timestamppb.Now(),
|
||||||
|
ResourceOwner: Instance.DefaultOrg.Id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "custom url template",
|
||||||
|
req: &user.SendEmailCodeRequest{
|
||||||
|
UserId: userID,
|
||||||
|
Verification: &user.SendEmailCodeRequest_SendCode{
|
||||||
|
SendCode: &user.SendEmailVerificationCode{
|
||||||
|
UrlTemplate: gu.Ptr("https://example.com/email/verify?userID={{.UserID}}&code={{.Code}}&orgID={{.OrgID}}"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: &user.SendEmailCodeResponse{
|
||||||
|
Details: &object.Details{
|
||||||
|
Sequence: 1,
|
||||||
|
ChangeDate: timestamppb.Now(),
|
||||||
|
ResourceOwner: Instance.DefaultOrg.Id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "template error",
|
||||||
|
req: &user.SendEmailCodeRequest{
|
||||||
|
UserId: userID,
|
||||||
|
Verification: &user.SendEmailCodeRequest_SendCode{
|
||||||
|
SendCode: &user.SendEmailVerificationCode{
|
||||||
|
UrlTemplate: gu.Ptr("{{"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "return code",
|
||||||
|
req: &user.SendEmailCodeRequest{
|
||||||
|
UserId: userID,
|
||||||
|
Verification: &user.SendEmailCodeRequest_ReturnCode{
|
||||||
|
ReturnCode: &user.ReturnEmailVerificationCode{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: &user.SendEmailCodeResponse{
|
||||||
|
Details: &object.Details{
|
||||||
|
Sequence: 1,
|
||||||
|
ChangeDate: timestamppb.Now(),
|
||||||
|
ResourceOwner: Instance.DefaultOrg.Id,
|
||||||
|
},
|
||||||
|
VerificationCode: gu.Ptr("xxx"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, err := Client.SendEmailCode(CTX, tt.req)
|
||||||
|
if tt.wantErr {
|
||||||
|
require.Error(t, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
integration.AssertDetails(t, tt.want, got)
|
||||||
|
if tt.want.GetVerificationCode() != "" {
|
||||||
|
assert.NotEmpty(t, got.GetVerificationCode())
|
||||||
|
} else {
|
||||||
|
assert.Empty(t, got.GetVerificationCode())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestServer_VerifyEmail(t *testing.T) {
|
func TestServer_VerifyEmail(t *testing.T) {
|
||||||
userResp := Instance.CreateHumanUser(CTX)
|
userResp := Instance.CreateHumanUser(CTX)
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
|
@ -102,17 +102,17 @@ func TestServer_ListIDPLinks(t *testing.T) {
|
|||||||
orgResp := Instance.CreateOrganization(IamCTX, fmt.Sprintf("ListIDPLinks-%s", gofakeit.AppName()), gofakeit.Email())
|
orgResp := Instance.CreateOrganization(IamCTX, fmt.Sprintf("ListIDPLinks-%s", gofakeit.AppName()), gofakeit.Email())
|
||||||
|
|
||||||
instanceIdpResp := Instance.AddGenericOAuthProvider(IamCTX, Instance.DefaultOrg.Id)
|
instanceIdpResp := Instance.AddGenericOAuthProvider(IamCTX, Instance.DefaultOrg.Id)
|
||||||
userInstanceResp := Instance.CreateHumanUserVerified(IamCTX, orgResp.OrganizationId, gofakeit.Email())
|
userInstanceResp := Instance.CreateHumanUserVerified(IamCTX, orgResp.OrganizationId, gofakeit.Email(), gofakeit.Phone())
|
||||||
_, err := Instance.CreateUserIDPlink(IamCTX, userInstanceResp.GetUserId(), "external_instance", instanceIdpResp.Id, "externalUsername_instance")
|
_, err := Instance.CreateUserIDPlink(IamCTX, userInstanceResp.GetUserId(), "external_instance", instanceIdpResp.Id, "externalUsername_instance")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
ctxOrg := metadata.AppendToOutgoingContext(IamCTX, "x-zitadel-orgid", orgResp.GetOrganizationId())
|
ctxOrg := metadata.AppendToOutgoingContext(IamCTX, "x-zitadel-orgid", orgResp.GetOrganizationId())
|
||||||
orgIdpResp := Instance.AddOrgGenericOAuthProvider(ctxOrg, orgResp.OrganizationId)
|
orgIdpResp := Instance.AddOrgGenericOAuthProvider(ctxOrg, orgResp.OrganizationId)
|
||||||
userOrgResp := Instance.CreateHumanUserVerified(ctxOrg, orgResp.OrganizationId, gofakeit.Email())
|
userOrgResp := Instance.CreateHumanUserVerified(ctxOrg, orgResp.OrganizationId, gofakeit.Email(), gofakeit.Phone())
|
||||||
_, err = Instance.CreateUserIDPlink(ctxOrg, userOrgResp.GetUserId(), "external_org", orgIdpResp.Id, "externalUsername_org")
|
_, err = Instance.CreateUserIDPlink(ctxOrg, userOrgResp.GetUserId(), "external_org", orgIdpResp.Id, "externalUsername_org")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
userMultipleResp := Instance.CreateHumanUserVerified(IamCTX, orgResp.OrganizationId, gofakeit.Email())
|
userMultipleResp := Instance.CreateHumanUserVerified(IamCTX, orgResp.OrganizationId, gofakeit.Email(), gofakeit.Phone())
|
||||||
_, err = Instance.CreateUserIDPlink(IamCTX, userMultipleResp.GetUserId(), "external_multi", instanceIdpResp.Id, "externalUsername_multi")
|
_, err = Instance.CreateUserIDPlink(IamCTX, userMultipleResp.GetUserId(), "external_multi", instanceIdpResp.Id, "externalUsername_multi")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
_, err = Instance.CreateUserIDPlink(ctxOrg, userMultipleResp.GetUserId(), "external_multi", orgIdpResp.Id, "externalUsername_multi")
|
_, err = Instance.CreateUserIDPlink(ctxOrg, userMultipleResp.GetUserId(), "external_multi", orgIdpResp.Id, "externalUsername_multi")
|
||||||
@ -256,17 +256,17 @@ func TestServer_RemoveIDPLink(t *testing.T) {
|
|||||||
orgResp := Instance.CreateOrganization(IamCTX, fmt.Sprintf("ListIDPLinks-%s", gofakeit.AppName()), gofakeit.Email())
|
orgResp := Instance.CreateOrganization(IamCTX, fmt.Sprintf("ListIDPLinks-%s", gofakeit.AppName()), gofakeit.Email())
|
||||||
|
|
||||||
instanceIdpResp := Instance.AddGenericOAuthProvider(IamCTX, Instance.DefaultOrg.Id)
|
instanceIdpResp := Instance.AddGenericOAuthProvider(IamCTX, Instance.DefaultOrg.Id)
|
||||||
userInstanceResp := Instance.CreateHumanUserVerified(IamCTX, orgResp.OrganizationId, gofakeit.Email())
|
userInstanceResp := Instance.CreateHumanUserVerified(IamCTX, orgResp.OrganizationId, gofakeit.Email(), gofakeit.Phone())
|
||||||
_, err := Instance.CreateUserIDPlink(IamCTX, userInstanceResp.GetUserId(), "external_instance", instanceIdpResp.Id, "externalUsername_instance")
|
_, err := Instance.CreateUserIDPlink(IamCTX, userInstanceResp.GetUserId(), "external_instance", instanceIdpResp.Id, "externalUsername_instance")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
ctxOrg := metadata.AppendToOutgoingContext(IamCTX, "x-zitadel-orgid", orgResp.GetOrganizationId())
|
ctxOrg := metadata.AppendToOutgoingContext(IamCTX, "x-zitadel-orgid", orgResp.GetOrganizationId())
|
||||||
orgIdpResp := Instance.AddOrgGenericOAuthProvider(ctxOrg, orgResp.OrganizationId)
|
orgIdpResp := Instance.AddOrgGenericOAuthProvider(ctxOrg, orgResp.OrganizationId)
|
||||||
userOrgResp := Instance.CreateHumanUserVerified(ctxOrg, orgResp.OrganizationId, gofakeit.Email())
|
userOrgResp := Instance.CreateHumanUserVerified(ctxOrg, orgResp.OrganizationId, gofakeit.Email(), gofakeit.Phone())
|
||||||
_, err = Instance.CreateUserIDPlink(ctxOrg, userOrgResp.GetUserId(), "external_org", orgIdpResp.Id, "externalUsername_org")
|
_, err = Instance.CreateUserIDPlink(ctxOrg, userOrgResp.GetUserId(), "external_org", orgIdpResp.Id, "externalUsername_org")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
userNoLinkResp := Instance.CreateHumanUserVerified(IamCTX, orgResp.OrganizationId, gofakeit.Email())
|
userNoLinkResp := Instance.CreateHumanUserVerified(IamCTX, orgResp.OrganizationId, gofakeit.Email(), gofakeit.Phone())
|
||||||
|
|
||||||
type args struct {
|
type args struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
|
@ -123,7 +123,7 @@ func TestServer_SetPhone(t *testing.T) {
|
|||||||
|
|
||||||
func TestServer_ResendPhoneCode(t *testing.T) {
|
func TestServer_ResendPhoneCode(t *testing.T) {
|
||||||
userID := Instance.CreateHumanUser(CTX).GetUserId()
|
userID := Instance.CreateHumanUser(CTX).GetUserId()
|
||||||
verifiedUserID := Instance.CreateHumanUserVerified(CTX, Instance.DefaultOrg.Id, gofakeit.Email()).GetUserId()
|
verifiedUserID := Instance.CreateHumanUserVerified(CTX, Instance.DefaultOrg.Id, gofakeit.Email(), gofakeit.Phone()).GetUserId()
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
@ -5,6 +5,7 @@ package user_test
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"slices"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -24,7 +25,7 @@ func TestServer_GetUserByID(t *testing.T) {
|
|||||||
type args struct {
|
type args struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
req *user.GetUserByIDRequest
|
req *user.GetUserByIDRequest
|
||||||
dep func(ctx context.Context, username string, request *user.GetUserByIDRequest) (*userAttr, error)
|
dep func(ctx context.Context, request *user.GetUserByIDRequest) *userAttr
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@ -39,8 +40,8 @@ func TestServer_GetUserByID(t *testing.T) {
|
|||||||
&user.GetUserByIDRequest{
|
&user.GetUserByIDRequest{
|
||||||
UserId: "",
|
UserId: "",
|
||||||
},
|
},
|
||||||
func(ctx context.Context, username string, request *user.GetUserByIDRequest) (*userAttr, error) {
|
func(ctx context.Context, request *user.GetUserByIDRequest) *userAttr {
|
||||||
return nil, nil
|
return nil
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
@ -52,8 +53,8 @@ func TestServer_GetUserByID(t *testing.T) {
|
|||||||
&user.GetUserByIDRequest{
|
&user.GetUserByIDRequest{
|
||||||
UserId: "unknown",
|
UserId: "unknown",
|
||||||
},
|
},
|
||||||
func(ctx context.Context, username string, request *user.GetUserByIDRequest) (*userAttr, error) {
|
func(ctx context.Context, request *user.GetUserByIDRequest) *userAttr {
|
||||||
return nil, nil
|
return nil
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
@ -63,10 +64,10 @@ func TestServer_GetUserByID(t *testing.T) {
|
|||||||
args: args{
|
args: args{
|
||||||
IamCTX,
|
IamCTX,
|
||||||
&user.GetUserByIDRequest{},
|
&user.GetUserByIDRequest{},
|
||||||
func(ctx context.Context, username string, request *user.GetUserByIDRequest) (*userAttr, error) {
|
func(ctx context.Context, request *user.GetUserByIDRequest) *userAttr {
|
||||||
resp := Instance.CreateHumanUserVerified(ctx, orgResp.OrganizationId, username)
|
info := createUser(ctx, orgResp.OrganizationId, false)
|
||||||
request.UserId = resp.GetUserId()
|
request.UserId = info.UserID
|
||||||
return &userAttr{resp.GetUserId(), username, nil, resp.GetDetails()}, nil
|
return &info
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
want: &user.GetUserByIDResponse{
|
want: &user.GetUserByIDResponse{
|
||||||
@ -90,7 +91,6 @@ func TestServer_GetUserByID(t *testing.T) {
|
|||||||
IsVerified: true,
|
IsVerified: true,
|
||||||
},
|
},
|
||||||
Phone: &user.HumanPhone{
|
Phone: &user.HumanPhone{
|
||||||
Phone: "+41791234567",
|
|
||||||
IsVerified: true,
|
IsVerified: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -107,11 +107,10 @@ func TestServer_GetUserByID(t *testing.T) {
|
|||||||
args: args{
|
args: args{
|
||||||
IamCTX,
|
IamCTX,
|
||||||
&user.GetUserByIDRequest{},
|
&user.GetUserByIDRequest{},
|
||||||
func(ctx context.Context, username string, request *user.GetUserByIDRequest) (*userAttr, error) {
|
func(ctx context.Context, request *user.GetUserByIDRequest) *userAttr {
|
||||||
resp := Instance.CreateHumanUserVerified(ctx, orgResp.OrganizationId, username)
|
info := createUser(ctx, orgResp.OrganizationId, true)
|
||||||
request.UserId = resp.GetUserId()
|
request.UserId = info.UserID
|
||||||
details := Instance.SetUserPassword(ctx, resp.GetUserId(), integration.UserPassword, true)
|
return &info
|
||||||
return &userAttr{resp.GetUserId(), username, details.GetChangeDate(), resp.GetDetails()}, nil
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
want: &user.GetUserByIDResponse{
|
want: &user.GetUserByIDResponse{
|
||||||
@ -135,7 +134,6 @@ func TestServer_GetUserByID(t *testing.T) {
|
|||||||
IsVerified: true,
|
IsVerified: true,
|
||||||
},
|
},
|
||||||
Phone: &user.HumanPhone{
|
Phone: &user.HumanPhone{
|
||||||
Phone: "+41791234567",
|
|
||||||
IsVerified: true,
|
IsVerified: true,
|
||||||
},
|
},
|
||||||
PasswordChangeRequired: true,
|
PasswordChangeRequired: true,
|
||||||
@ -152,9 +150,7 @@ func TestServer_GetUserByID(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
username := gofakeit.Email()
|
userAttr := tt.args.dep(IamCTX, tt.args.req)
|
||||||
userAttr, err := tt.args.dep(tt.args.ctx, username, tt.args.req)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(tt.args.ctx, time.Minute)
|
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(tt.args.ctx, time.Minute)
|
||||||
require.EventuallyWithT(t, func(ttt *assert.CollectT) {
|
require.EventuallyWithT(t, func(ttt *assert.CollectT) {
|
||||||
@ -174,11 +170,12 @@ func TestServer_GetUserByID(t *testing.T) {
|
|||||||
tt.want.User.LoginNames = []string{userAttr.Username}
|
tt.want.User.LoginNames = []string{userAttr.Username}
|
||||||
if human := tt.want.User.GetHuman(); human != nil {
|
if human := tt.want.User.GetHuman(); human != nil {
|
||||||
human.Email.Email = userAttr.Username
|
human.Email.Email = userAttr.Username
|
||||||
|
human.Phone.Phone = userAttr.Phone
|
||||||
if tt.want.User.GetHuman().GetPasswordChanged() != nil {
|
if tt.want.User.GetHuman().GetPasswordChanged() != nil {
|
||||||
human.PasswordChanged = userAttr.Changed
|
human.PasswordChanged = userAttr.Changed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
assert.Equal(ttt, tt.want.User, got.User)
|
assert.EqualExportedValues(ttt, tt.want.User, got.User)
|
||||||
integration.AssertDetails(ttt, tt.want, got)
|
integration.AssertDetails(ttt, tt.want, got)
|
||||||
}, retryDuration, tick)
|
}, retryDuration, tick)
|
||||||
})
|
})
|
||||||
@ -325,21 +322,60 @@ func TestServer_GetUserByID_Permission(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type userAttrs []userAttr
|
||||||
|
|
||||||
|
func (u userAttrs) userIDs() []string {
|
||||||
|
ids := make([]string, len(u))
|
||||||
|
for i := range u {
|
||||||
|
ids[i] = u[i].UserID
|
||||||
|
}
|
||||||
|
return ids
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u userAttrs) emails() []string {
|
||||||
|
emails := make([]string, len(u))
|
||||||
|
for i := range u {
|
||||||
|
emails[i] = u[i].Username
|
||||||
|
}
|
||||||
|
return emails
|
||||||
|
}
|
||||||
|
|
||||||
type userAttr struct {
|
type userAttr struct {
|
||||||
UserID string
|
UserID string
|
||||||
Username string
|
Username string
|
||||||
|
Phone string
|
||||||
Changed *timestamppb.Timestamp
|
Changed *timestamppb.Timestamp
|
||||||
Details *object.Details
|
Details *object.Details
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func createUsers(ctx context.Context, orgID string, count int, passwordChangeRequired bool) userAttrs {
|
||||||
|
infos := make([]userAttr, count)
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
infos[i] = createUser(ctx, orgID, passwordChangeRequired)
|
||||||
|
}
|
||||||
|
slices.Reverse(infos)
|
||||||
|
return infos
|
||||||
|
}
|
||||||
|
|
||||||
|
func createUser(ctx context.Context, orgID string, passwordChangeRequired bool) userAttr {
|
||||||
|
username := gofakeit.Email()
|
||||||
|
// used as default country prefix
|
||||||
|
phone := "+41" + gofakeit.Phone()
|
||||||
|
resp := Instance.CreateHumanUserVerified(ctx, orgID, username, phone)
|
||||||
|
info := userAttr{resp.GetUserId(), username, phone, nil, resp.GetDetails()}
|
||||||
|
if passwordChangeRequired {
|
||||||
|
details := Instance.SetUserPassword(ctx, resp.GetUserId(), integration.UserPassword, true)
|
||||||
|
info.Changed = details.GetChangeDate()
|
||||||
|
}
|
||||||
|
return info
|
||||||
|
}
|
||||||
|
|
||||||
func TestServer_ListUsers(t *testing.T) {
|
func TestServer_ListUsers(t *testing.T) {
|
||||||
orgResp := Instance.CreateOrganization(IamCTX, fmt.Sprintf("ListUsersOrg-%s", gofakeit.AppName()), gofakeit.Email())
|
orgResp := Instance.CreateOrganization(IamCTX, fmt.Sprintf("ListUsersOrg-%s", gofakeit.AppName()), gofakeit.Email())
|
||||||
userResp := Instance.CreateHumanUserVerified(IamCTX, orgResp.OrganizationId, gofakeit.Email())
|
|
||||||
type args struct {
|
type args struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
count int
|
req *user.ListUsersRequest
|
||||||
req *user.ListUsersRequest
|
dep func(ctx context.Context, request *user.ListUsersRequest) userAttrs
|
||||||
dep func(ctx context.Context, usernames []string, request *user.ListUsersRequest) ([]userAttr, error)
|
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@ -351,11 +387,11 @@ func TestServer_ListUsers(t *testing.T) {
|
|||||||
name: "list user by id, no permission",
|
name: "list user by id, no permission",
|
||||||
args: args{
|
args: args{
|
||||||
UserCTX,
|
UserCTX,
|
||||||
0,
|
|
||||||
&user.ListUsersRequest{},
|
&user.ListUsersRequest{},
|
||||||
func(ctx context.Context, usernames []string, request *user.ListUsersRequest) ([]userAttr, error) {
|
func(ctx context.Context, request *user.ListUsersRequest) userAttrs {
|
||||||
request.Queries = append(request.Queries, InUserIDsQuery([]string{userResp.UserId}))
|
info := createUser(ctx, orgResp.OrganizationId, false)
|
||||||
return []userAttr{}, nil
|
request.Queries = append(request.Queries, InUserIDsQuery([]string{info.UserID}))
|
||||||
|
return []userAttr{}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
want: &user.ListUsersResponse{
|
want: &user.ListUsersResponse{
|
||||||
@ -371,22 +407,15 @@ func TestServer_ListUsers(t *testing.T) {
|
|||||||
name: "list user by id, ok",
|
name: "list user by id, ok",
|
||||||
args: args{
|
args: args{
|
||||||
IamCTX,
|
IamCTX,
|
||||||
1,
|
|
||||||
&user.ListUsersRequest{
|
&user.ListUsersRequest{
|
||||||
Queries: []*user.SearchQuery{
|
Queries: []*user.SearchQuery{
|
||||||
OrganizationIdQuery(orgResp.OrganizationId),
|
OrganizationIdQuery(orgResp.OrganizationId),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
func(ctx context.Context, usernames []string, request *user.ListUsersRequest) ([]userAttr, error) {
|
func(ctx context.Context, request *user.ListUsersRequest) userAttrs {
|
||||||
infos := make([]userAttr, len(usernames))
|
info := createUser(ctx, orgResp.OrganizationId, false)
|
||||||
userIDs := make([]string, len(usernames))
|
request.Queries = append(request.Queries, InUserIDsQuery([]string{info.UserID}))
|
||||||
for i, username := range usernames {
|
return []userAttr{info}
|
||||||
resp := Instance.CreateHumanUserVerified(ctx, orgResp.OrganizationId, username)
|
|
||||||
userIDs[i] = resp.GetUserId()
|
|
||||||
infos[i] = userAttr{resp.GetUserId(), username, nil, resp.GetDetails()}
|
|
||||||
}
|
|
||||||
request.Queries = append(request.Queries, InUserIDsQuery(userIDs))
|
|
||||||
return infos, nil
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
want: &user.ListUsersResponse{
|
want: &user.ListUsersResponse{
|
||||||
@ -412,7 +441,6 @@ func TestServer_ListUsers(t *testing.T) {
|
|||||||
IsVerified: true,
|
IsVerified: true,
|
||||||
},
|
},
|
||||||
Phone: &user.HumanPhone{
|
Phone: &user.HumanPhone{
|
||||||
Phone: "+41791234567",
|
|
||||||
IsVerified: true,
|
IsVerified: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -425,23 +453,15 @@ func TestServer_ListUsers(t *testing.T) {
|
|||||||
name: "list user by id, passwordChangeRequired, ok",
|
name: "list user by id, passwordChangeRequired, ok",
|
||||||
args: args{
|
args: args{
|
||||||
IamCTX,
|
IamCTX,
|
||||||
1,
|
|
||||||
&user.ListUsersRequest{
|
&user.ListUsersRequest{
|
||||||
Queries: []*user.SearchQuery{
|
Queries: []*user.SearchQuery{
|
||||||
OrganizationIdQuery(orgResp.OrganizationId),
|
OrganizationIdQuery(orgResp.OrganizationId),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
func(ctx context.Context, usernames []string, request *user.ListUsersRequest) ([]userAttr, error) {
|
func(ctx context.Context, request *user.ListUsersRequest) userAttrs {
|
||||||
infos := make([]userAttr, len(usernames))
|
info := createUser(ctx, orgResp.OrganizationId, true)
|
||||||
userIDs := make([]string, len(usernames))
|
request.Queries = append(request.Queries, InUserIDsQuery([]string{info.UserID}))
|
||||||
for i, username := range usernames {
|
return []userAttr{info}
|
||||||
resp := Instance.CreateHumanUserVerified(ctx, orgResp.OrganizationId, username)
|
|
||||||
userIDs[i] = resp.GetUserId()
|
|
||||||
details := Instance.SetUserPassword(ctx, resp.GetUserId(), integration.UserPassword, true)
|
|
||||||
infos[i] = userAttr{resp.GetUserId(), username, details.GetChangeDate(), resp.GetDetails()}
|
|
||||||
}
|
|
||||||
request.Queries = append(request.Queries, InUserIDsQuery(userIDs))
|
|
||||||
return infos, nil
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
want: &user.ListUsersResponse{
|
want: &user.ListUsersResponse{
|
||||||
@ -467,7 +487,6 @@ func TestServer_ListUsers(t *testing.T) {
|
|||||||
IsVerified: true,
|
IsVerified: true,
|
||||||
},
|
},
|
||||||
Phone: &user.HumanPhone{
|
Phone: &user.HumanPhone{
|
||||||
Phone: "+41791234567",
|
|
||||||
IsVerified: true,
|
IsVerified: true,
|
||||||
},
|
},
|
||||||
PasswordChangeRequired: true,
|
PasswordChangeRequired: true,
|
||||||
@ -482,22 +501,15 @@ func TestServer_ListUsers(t *testing.T) {
|
|||||||
name: "list user by id multiple, ok",
|
name: "list user by id multiple, ok",
|
||||||
args: args{
|
args: args{
|
||||||
IamCTX,
|
IamCTX,
|
||||||
3,
|
|
||||||
&user.ListUsersRequest{
|
&user.ListUsersRequest{
|
||||||
Queries: []*user.SearchQuery{
|
Queries: []*user.SearchQuery{
|
||||||
OrganizationIdQuery(orgResp.OrganizationId),
|
OrganizationIdQuery(orgResp.OrganizationId),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
func(ctx context.Context, usernames []string, request *user.ListUsersRequest) ([]userAttr, error) {
|
func(ctx context.Context, request *user.ListUsersRequest) userAttrs {
|
||||||
infos := make([]userAttr, len(usernames))
|
infos := createUsers(ctx, orgResp.OrganizationId, 3, false)
|
||||||
userIDs := make([]string, len(usernames))
|
request.Queries = append(request.Queries, InUserIDsQuery(infos.userIDs()))
|
||||||
for i, username := range usernames {
|
return infos
|
||||||
resp := Instance.CreateHumanUserVerified(ctx, orgResp.OrganizationId, username)
|
|
||||||
userIDs[i] = resp.GetUserId()
|
|
||||||
infos[i] = userAttr{resp.GetUserId(), username, nil, resp.GetDetails()}
|
|
||||||
}
|
|
||||||
request.Queries = append(request.Queries, InUserIDsQuery(userIDs))
|
|
||||||
return infos, nil
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
want: &user.ListUsersResponse{
|
want: &user.ListUsersResponse{
|
||||||
@ -523,7 +535,27 @@ func TestServer_ListUsers(t *testing.T) {
|
|||||||
IsVerified: true,
|
IsVerified: true,
|
||||||
},
|
},
|
||||||
Phone: &user.HumanPhone{
|
Phone: &user.HumanPhone{
|
||||||
Phone: "+41791234567",
|
IsVerified: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
State: user.UserState_USER_STATE_ACTIVE,
|
||||||
|
Type: &user.User_Human{
|
||||||
|
Human: &user.HumanUser{
|
||||||
|
Profile: &user.HumanProfile{
|
||||||
|
GivenName: "Mickey",
|
||||||
|
FamilyName: "Mouse",
|
||||||
|
NickName: gu.Ptr("Mickey"),
|
||||||
|
DisplayName: gu.Ptr("Mickey Mouse"),
|
||||||
|
PreferredLanguage: gu.Ptr("nl"),
|
||||||
|
Gender: user.Gender_GENDER_MALE.Enum(),
|
||||||
|
},
|
||||||
|
Email: &user.HumanEmail{
|
||||||
|
IsVerified: true,
|
||||||
|
},
|
||||||
|
Phone: &user.HumanPhone{
|
||||||
IsVerified: true,
|
IsVerified: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -544,28 +576,6 @@ func TestServer_ListUsers(t *testing.T) {
|
|||||||
IsVerified: true,
|
IsVerified: true,
|
||||||
},
|
},
|
||||||
Phone: &user.HumanPhone{
|
Phone: &user.HumanPhone{
|
||||||
Phone: "+41791234567",
|
|
||||||
IsVerified: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
State: user.UserState_USER_STATE_ACTIVE,
|
|
||||||
Type: &user.User_Human{
|
|
||||||
Human: &user.HumanUser{
|
|
||||||
Profile: &user.HumanProfile{
|
|
||||||
GivenName: "Mickey",
|
|
||||||
FamilyName: "Mouse",
|
|
||||||
NickName: gu.Ptr("Mickey"),
|
|
||||||
DisplayName: gu.Ptr("Mickey Mouse"),
|
|
||||||
PreferredLanguage: gu.Ptr("nl"),
|
|
||||||
Gender: user.Gender_GENDER_MALE.Enum(),
|
|
||||||
},
|
|
||||||
Email: &user.HumanEmail{
|
|
||||||
IsVerified: true,
|
|
||||||
},
|
|
||||||
Phone: &user.HumanPhone{
|
|
||||||
Phone: "+41791234567",
|
|
||||||
IsVerified: true,
|
IsVerified: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -578,22 +588,15 @@ func TestServer_ListUsers(t *testing.T) {
|
|||||||
name: "list user by username, ok",
|
name: "list user by username, ok",
|
||||||
args: args{
|
args: args{
|
||||||
IamCTX,
|
IamCTX,
|
||||||
1,
|
|
||||||
&user.ListUsersRequest{
|
&user.ListUsersRequest{
|
||||||
Queries: []*user.SearchQuery{
|
Queries: []*user.SearchQuery{
|
||||||
OrganizationIdQuery(orgResp.OrganizationId),
|
OrganizationIdQuery(orgResp.OrganizationId),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
func(ctx context.Context, usernames []string, request *user.ListUsersRequest) ([]userAttr, error) {
|
func(ctx context.Context, request *user.ListUsersRequest) userAttrs {
|
||||||
infos := make([]userAttr, len(usernames))
|
info := createUser(ctx, orgResp.OrganizationId, false)
|
||||||
userIDs := make([]string, len(usernames))
|
request.Queries = append(request.Queries, UsernameQuery(info.Username))
|
||||||
for i, username := range usernames {
|
return []userAttr{info}
|
||||||
resp := Instance.CreateHumanUserVerified(ctx, orgResp.OrganizationId, username)
|
|
||||||
userIDs[i] = resp.GetUserId()
|
|
||||||
infos[i] = userAttr{resp.GetUserId(), username, nil, resp.GetDetails()}
|
|
||||||
request.Queries = append(request.Queries, UsernameQuery(username))
|
|
||||||
}
|
|
||||||
return infos, nil
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
want: &user.ListUsersResponse{
|
want: &user.ListUsersResponse{
|
||||||
@ -619,7 +622,6 @@ func TestServer_ListUsers(t *testing.T) {
|
|||||||
IsVerified: true,
|
IsVerified: true,
|
||||||
},
|
},
|
||||||
Phone: &user.HumanPhone{
|
Phone: &user.HumanPhone{
|
||||||
Phone: "+41791234567",
|
|
||||||
IsVerified: true,
|
IsVerified: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -632,20 +634,15 @@ func TestServer_ListUsers(t *testing.T) {
|
|||||||
name: "list user in emails, ok",
|
name: "list user in emails, ok",
|
||||||
args: args{
|
args: args{
|
||||||
IamCTX,
|
IamCTX,
|
||||||
1,
|
|
||||||
&user.ListUsersRequest{
|
&user.ListUsersRequest{
|
||||||
Queries: []*user.SearchQuery{
|
Queries: []*user.SearchQuery{
|
||||||
OrganizationIdQuery(orgResp.OrganizationId),
|
OrganizationIdQuery(orgResp.OrganizationId),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
func(ctx context.Context, usernames []string, request *user.ListUsersRequest) ([]userAttr, error) {
|
func(ctx context.Context, request *user.ListUsersRequest) userAttrs {
|
||||||
infos := make([]userAttr, len(usernames))
|
info := createUser(ctx, orgResp.OrganizationId, false)
|
||||||
for i, username := range usernames {
|
request.Queries = append(request.Queries, InUserEmailsQuery([]string{info.Username}))
|
||||||
resp := Instance.CreateHumanUserVerified(ctx, orgResp.OrganizationId, username)
|
return []userAttr{info}
|
||||||
infos[i] = userAttr{resp.GetUserId(), username, nil, resp.GetDetails()}
|
|
||||||
}
|
|
||||||
request.Queries = append(request.Queries, InUserEmailsQuery(usernames))
|
|
||||||
return infos, nil
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
want: &user.ListUsersResponse{
|
want: &user.ListUsersResponse{
|
||||||
@ -671,7 +668,6 @@ func TestServer_ListUsers(t *testing.T) {
|
|||||||
IsVerified: true,
|
IsVerified: true,
|
||||||
},
|
},
|
||||||
Phone: &user.HumanPhone{
|
Phone: &user.HumanPhone{
|
||||||
Phone: "+41791234567",
|
|
||||||
IsVerified: true,
|
IsVerified: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -684,20 +680,15 @@ func TestServer_ListUsers(t *testing.T) {
|
|||||||
name: "list user in emails multiple, ok",
|
name: "list user in emails multiple, ok",
|
||||||
args: args{
|
args: args{
|
||||||
IamCTX,
|
IamCTX,
|
||||||
3,
|
|
||||||
&user.ListUsersRequest{
|
&user.ListUsersRequest{
|
||||||
Queries: []*user.SearchQuery{
|
Queries: []*user.SearchQuery{
|
||||||
OrganizationIdQuery(orgResp.OrganizationId),
|
OrganizationIdQuery(orgResp.OrganizationId),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
func(ctx context.Context, usernames []string, request *user.ListUsersRequest) ([]userAttr, error) {
|
func(ctx context.Context, request *user.ListUsersRequest) userAttrs {
|
||||||
infos := make([]userAttr, len(usernames))
|
infos := createUsers(ctx, orgResp.OrganizationId, 3, false)
|
||||||
for i, username := range usernames {
|
request.Queries = append(request.Queries, InUserEmailsQuery(infos.emails()))
|
||||||
resp := Instance.CreateHumanUserVerified(ctx, orgResp.OrganizationId, username)
|
return infos
|
||||||
infos[i] = userAttr{resp.GetUserId(), username, nil, resp.GetDetails()}
|
|
||||||
}
|
|
||||||
request.Queries = append(request.Queries, InUserEmailsQuery(usernames))
|
|
||||||
return infos, nil
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
want: &user.ListUsersResponse{
|
want: &user.ListUsersResponse{
|
||||||
@ -723,7 +714,6 @@ func TestServer_ListUsers(t *testing.T) {
|
|||||||
IsVerified: true,
|
IsVerified: true,
|
||||||
},
|
},
|
||||||
Phone: &user.HumanPhone{
|
Phone: &user.HumanPhone{
|
||||||
Phone: "+41791234567",
|
|
||||||
IsVerified: true,
|
IsVerified: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -744,7 +734,6 @@ func TestServer_ListUsers(t *testing.T) {
|
|||||||
IsVerified: true,
|
IsVerified: true,
|
||||||
},
|
},
|
||||||
Phone: &user.HumanPhone{
|
Phone: &user.HumanPhone{
|
||||||
Phone: "+41791234567",
|
|
||||||
IsVerified: true,
|
IsVerified: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -765,7 +754,6 @@ func TestServer_ListUsers(t *testing.T) {
|
|||||||
IsVerified: true,
|
IsVerified: true,
|
||||||
},
|
},
|
||||||
Phone: &user.HumanPhone{
|
Phone: &user.HumanPhone{
|
||||||
Phone: "+41791234567",
|
|
||||||
IsVerified: true,
|
IsVerified: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -778,14 +766,81 @@ func TestServer_ListUsers(t *testing.T) {
|
|||||||
name: "list user in emails no found, ok",
|
name: "list user in emails no found, ok",
|
||||||
args: args{
|
args: args{
|
||||||
IamCTX,
|
IamCTX,
|
||||||
3,
|
|
||||||
&user.ListUsersRequest{Queries: []*user.SearchQuery{
|
&user.ListUsersRequest{Queries: []*user.SearchQuery{
|
||||||
OrganizationIdQuery(orgResp.OrganizationId),
|
OrganizationIdQuery(orgResp.OrganizationId),
|
||||||
InUserEmailsQuery([]string{"notfound"}),
|
InUserEmailsQuery([]string{"notfound"}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
func(ctx context.Context, usernames []string, request *user.ListUsersRequest) ([]userAttr, error) {
|
func(ctx context.Context, request *user.ListUsersRequest) userAttrs {
|
||||||
return []userAttr{}, nil
|
return []userAttr{}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: &user.ListUsersResponse{
|
||||||
|
Details: &object.ListDetails{
|
||||||
|
TotalResult: 0,
|
||||||
|
Timestamp: timestamppb.Now(),
|
||||||
|
},
|
||||||
|
SortingColumn: 0,
|
||||||
|
Result: []*user.User{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "list user phone, ok",
|
||||||
|
args: args{
|
||||||
|
IamCTX,
|
||||||
|
&user.ListUsersRequest{
|
||||||
|
Queries: []*user.SearchQuery{
|
||||||
|
OrganizationIdQuery(orgResp.OrganizationId),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
func(ctx context.Context, request *user.ListUsersRequest) userAttrs {
|
||||||
|
info := createUser(ctx, orgResp.OrganizationId, false)
|
||||||
|
request.Queries = append(request.Queries, PhoneQuery(info.Phone))
|
||||||
|
return []userAttr{info}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: &user.ListUsersResponse{
|
||||||
|
Details: &object.ListDetails{
|
||||||
|
TotalResult: 1,
|
||||||
|
Timestamp: timestamppb.Now(),
|
||||||
|
},
|
||||||
|
SortingColumn: 0,
|
||||||
|
Result: []*user.User{
|
||||||
|
{
|
||||||
|
State: user.UserState_USER_STATE_ACTIVE,
|
||||||
|
Type: &user.User_Human{
|
||||||
|
Human: &user.HumanUser{
|
||||||
|
Profile: &user.HumanProfile{
|
||||||
|
GivenName: "Mickey",
|
||||||
|
FamilyName: "Mouse",
|
||||||
|
NickName: gu.Ptr("Mickey"),
|
||||||
|
DisplayName: gu.Ptr("Mickey Mouse"),
|
||||||
|
PreferredLanguage: gu.Ptr("nl"),
|
||||||
|
Gender: user.Gender_GENDER_MALE.Enum(),
|
||||||
|
},
|
||||||
|
Email: &user.HumanEmail{
|
||||||
|
IsVerified: true,
|
||||||
|
},
|
||||||
|
Phone: &user.HumanPhone{
|
||||||
|
IsVerified: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "list user in emails no found, ok",
|
||||||
|
args: args{
|
||||||
|
IamCTX,
|
||||||
|
&user.ListUsersRequest{Queries: []*user.SearchQuery{
|
||||||
|
OrganizationIdQuery(orgResp.OrganizationId),
|
||||||
|
InUserEmailsQuery([]string{"notfound"}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
func(ctx context.Context, request *user.ListUsersRequest) userAttrs {
|
||||||
|
return []userAttr{}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
want: &user.ListUsersResponse{
|
want: &user.ListUsersResponse{
|
||||||
@ -801,19 +856,14 @@ func TestServer_ListUsers(t *testing.T) {
|
|||||||
name: "list user resourceowner multiple, ok",
|
name: "list user resourceowner multiple, ok",
|
||||||
args: args{
|
args: args{
|
||||||
IamCTX,
|
IamCTX,
|
||||||
3,
|
|
||||||
&user.ListUsersRequest{},
|
&user.ListUsersRequest{},
|
||||||
func(ctx context.Context, usernames []string, request *user.ListUsersRequest) ([]userAttr, error) {
|
func(ctx context.Context, request *user.ListUsersRequest) userAttrs {
|
||||||
orgResp := Instance.CreateOrganization(ctx, fmt.Sprintf("ListUsersResourceowner-%s", gofakeit.AppName()), gofakeit.Email())
|
orgResp := Instance.CreateOrganization(ctx, fmt.Sprintf("ListUsersResourceowner-%s", gofakeit.AppName()), gofakeit.Email())
|
||||||
|
|
||||||
infos := make([]userAttr, len(usernames))
|
infos := createUsers(ctx, orgResp.OrganizationId, 3, false)
|
||||||
for i, username := range usernames {
|
|
||||||
resp := Instance.CreateHumanUserVerified(ctx, orgResp.OrganizationId, username)
|
|
||||||
infos[i] = userAttr{resp.GetUserId(), username, nil, resp.GetDetails()}
|
|
||||||
}
|
|
||||||
request.Queries = append(request.Queries, OrganizationIdQuery(orgResp.OrganizationId))
|
request.Queries = append(request.Queries, OrganizationIdQuery(orgResp.OrganizationId))
|
||||||
request.Queries = append(request.Queries, InUserEmailsQuery(usernames))
|
request.Queries = append(request.Queries, InUserEmailsQuery(infos.emails()))
|
||||||
return infos, nil
|
return infos
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
want: &user.ListUsersResponse{
|
want: &user.ListUsersResponse{
|
||||||
@ -839,7 +889,6 @@ func TestServer_ListUsers(t *testing.T) {
|
|||||||
IsVerified: true,
|
IsVerified: true,
|
||||||
},
|
},
|
||||||
Phone: &user.HumanPhone{
|
Phone: &user.HumanPhone{
|
||||||
Phone: "+41791234567",
|
|
||||||
IsVerified: true,
|
IsVerified: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -860,7 +909,6 @@ func TestServer_ListUsers(t *testing.T) {
|
|||||||
IsVerified: true,
|
IsVerified: true,
|
||||||
},
|
},
|
||||||
Phone: &user.HumanPhone{
|
Phone: &user.HumanPhone{
|
||||||
Phone: "+41791234567",
|
|
||||||
IsVerified: true,
|
IsVerified: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -881,7 +929,6 @@ func TestServer_ListUsers(t *testing.T) {
|
|||||||
IsVerified: true,
|
IsVerified: true,
|
||||||
},
|
},
|
||||||
Phone: &user.HumanPhone{
|
Phone: &user.HumanPhone{
|
||||||
Phone: "+41791234567",
|
|
||||||
IsVerified: true,
|
IsVerified: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -893,12 +940,7 @@ func TestServer_ListUsers(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
usernames := make([]string, tt.args.count)
|
infos := tt.args.dep(IamCTX, tt.args.req)
|
||||||
for i := 0; i < tt.args.count; i++ {
|
|
||||||
usernames[i] = gofakeit.Email()
|
|
||||||
}
|
|
||||||
infos, err := tt.args.dep(tt.args.ctx, usernames, tt.args.req)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(tt.args.ctx, time.Minute)
|
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(tt.args.ctx, time.Minute)
|
||||||
require.EventuallyWithT(t, func(ttt *assert.CollectT) {
|
require.EventuallyWithT(t, func(ttt *assert.CollectT) {
|
||||||
@ -924,6 +966,7 @@ func TestServer_ListUsers(t *testing.T) {
|
|||||||
tt.want.Result[i].LoginNames = []string{infos[i].Username}
|
tt.want.Result[i].LoginNames = []string{infos[i].Username}
|
||||||
if human := tt.want.Result[i].GetHuman(); human != nil {
|
if human := tt.want.Result[i].GetHuman(); human != nil {
|
||||||
human.Email.Email = infos[i].Username
|
human.Email.Email = infos[i].Username
|
||||||
|
human.Phone.Phone = infos[i].Phone
|
||||||
if tt.want.Result[i].GetHuman().GetPasswordChanged() != nil {
|
if tt.want.Result[i].GetHuman().GetPasswordChanged() != nil {
|
||||||
human.PasswordChanged = infos[i].Changed
|
human.PasswordChanged = infos[i].Changed
|
||||||
}
|
}
|
||||||
@ -931,7 +974,7 @@ func TestServer_ListUsers(t *testing.T) {
|
|||||||
tt.want.Result[i].Details = infos[i].Details
|
tt.want.Result[i].Details = infos[i].Details
|
||||||
}
|
}
|
||||||
for i := range tt.want.Result {
|
for i := range tt.want.Result {
|
||||||
assert.Contains(ttt, got.Result, tt.want.Result[i])
|
assert.EqualExportedValues(ttt, got.Result[i], tt.want.Result[i])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
integration.AssertListDetails(ttt, tt.want, got)
|
integration.AssertListDetails(ttt, tt.want, got)
|
||||||
@ -958,6 +1001,15 @@ func InUserEmailsQuery(emails []string) *user.SearchQuery {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func PhoneQuery(number string) *user.SearchQuery {
|
||||||
|
return &user.SearchQuery{Query: &user.SearchQuery_PhoneQuery{
|
||||||
|
PhoneQuery: &user.PhoneQuery{
|
||||||
|
Number: number,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func UsernameQuery(username string) *user.SearchQuery {
|
func UsernameQuery(username string) *user.SearchQuery {
|
||||||
return &user.SearchQuery{Query: &user.SearchQuery_UserNameQuery{
|
return &user.SearchQuery{Query: &user.SearchQuery_UserNameQuery{
|
||||||
UserNameQuery: &user.UserNameQuery{
|
UserNameQuery: &user.UserNameQuery{
|
||||||
|
@ -10,6 +10,8 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/zitadel/logging"
|
||||||
|
|
||||||
"github.com/brianvoe/gofakeit/v6"
|
"github.com/brianvoe/gofakeit/v6"
|
||||||
"github.com/muhlemmer/gu"
|
"github.com/muhlemmer/gu"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@ -2629,6 +2631,247 @@ func TestServer_ListAuthenticationMethodTypes(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestServer_ListAuthenticationFactors(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args *user.ListAuthenticationFactorsRequest
|
||||||
|
want *user.ListAuthenticationFactorsResponse
|
||||||
|
dep func(args *user.ListAuthenticationFactorsRequest, want *user.ListAuthenticationFactorsResponse)
|
||||||
|
wantErr bool
|
||||||
|
ctx context.Context
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no auth",
|
||||||
|
args: &user.ListAuthenticationFactorsRequest{},
|
||||||
|
want: &user.ListAuthenticationFactorsResponse{
|
||||||
|
Result: nil,
|
||||||
|
},
|
||||||
|
dep: func(args *user.ListAuthenticationFactorsRequest, want *user.ListAuthenticationFactorsResponse) {
|
||||||
|
userIDWithoutAuth := Instance.CreateHumanUser(CTX).GetUserId()
|
||||||
|
args.UserId = userIDWithoutAuth
|
||||||
|
},
|
||||||
|
ctx: CTX,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with u2f",
|
||||||
|
args: &user.ListAuthenticationFactorsRequest{},
|
||||||
|
want: &user.ListAuthenticationFactorsResponse{
|
||||||
|
Result: []*user.AuthFactor{
|
||||||
|
{
|
||||||
|
State: user.AuthFactorState_AUTH_FACTOR_STATE_READY,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dep: func(args *user.ListAuthenticationFactorsRequest, want *user.ListAuthenticationFactorsResponse) {
|
||||||
|
userWithU2F := Instance.CreateHumanUser(CTX).GetUserId()
|
||||||
|
U2FId := Instance.RegisterUserU2F(CTX, userWithU2F)
|
||||||
|
|
||||||
|
args.UserId = userWithU2F
|
||||||
|
want.Result[0].Type = &user.AuthFactor_U2F{
|
||||||
|
U2F: &user.AuthFactorU2F{
|
||||||
|
Id: U2FId,
|
||||||
|
Name: "nice name",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ctx: CTX,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with totp, u2f",
|
||||||
|
args: &user.ListAuthenticationFactorsRequest{},
|
||||||
|
want: &user.ListAuthenticationFactorsResponse{
|
||||||
|
Result: []*user.AuthFactor{
|
||||||
|
{
|
||||||
|
State: user.AuthFactorState_AUTH_FACTOR_STATE_READY,
|
||||||
|
Type: &user.AuthFactor_Otp{
|
||||||
|
Otp: &user.AuthFactorOTP{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
State: user.AuthFactorState_AUTH_FACTOR_STATE_READY,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dep: func(args *user.ListAuthenticationFactorsRequest, want *user.ListAuthenticationFactorsResponse) {
|
||||||
|
userWithTOTP := Instance.CreateHumanUserWithTOTP(CTX, "secret").GetUserId()
|
||||||
|
U2FIdWithTOTP := Instance.RegisterUserU2F(CTX, userWithTOTP)
|
||||||
|
|
||||||
|
args.UserId = userWithTOTP
|
||||||
|
want.Result[1].Type = &user.AuthFactor_U2F{
|
||||||
|
U2F: &user.AuthFactorU2F{
|
||||||
|
Id: U2FIdWithTOTP,
|
||||||
|
Name: "nice name",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ctx: CTX,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with totp, u2f filtered",
|
||||||
|
args: &user.ListAuthenticationFactorsRequest{
|
||||||
|
AuthFactors: []user.AuthFactors{user.AuthFactors_U2F},
|
||||||
|
},
|
||||||
|
want: &user.ListAuthenticationFactorsResponse{
|
||||||
|
Result: []*user.AuthFactor{
|
||||||
|
{
|
||||||
|
State: user.AuthFactorState_AUTH_FACTOR_STATE_READY,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dep: func(args *user.ListAuthenticationFactorsRequest, want *user.ListAuthenticationFactorsResponse) {
|
||||||
|
userWithTOTP := Instance.CreateHumanUserWithTOTP(CTX, "secret").GetUserId()
|
||||||
|
U2FIdWithTOTP := Instance.RegisterUserU2F(CTX, userWithTOTP)
|
||||||
|
|
||||||
|
args.UserId = userWithTOTP
|
||||||
|
want.Result[0].Type = &user.AuthFactor_U2F{
|
||||||
|
U2F: &user.AuthFactorU2F{
|
||||||
|
Id: U2FIdWithTOTP,
|
||||||
|
Name: "nice name",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ctx: CTX,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with sms",
|
||||||
|
args: &user.ListAuthenticationFactorsRequest{},
|
||||||
|
want: &user.ListAuthenticationFactorsResponse{
|
||||||
|
Result: []*user.AuthFactor{
|
||||||
|
{
|
||||||
|
State: user.AuthFactorState_AUTH_FACTOR_STATE_READY,
|
||||||
|
Type: &user.AuthFactor_OtpSms{
|
||||||
|
OtpSms: &user.AuthFactorOTPSMS{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dep: func(args *user.ListAuthenticationFactorsRequest, want *user.ListAuthenticationFactorsResponse) {
|
||||||
|
userWithSMS := Instance.CreateHumanUserVerified(CTX, Instance.DefaultOrg.GetId(), gofakeit.Email(), gofakeit.Phone()).GetUserId()
|
||||||
|
Instance.RegisterUserOTPSMS(CTX, userWithSMS)
|
||||||
|
|
||||||
|
args.UserId = userWithSMS
|
||||||
|
},
|
||||||
|
ctx: CTX,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with email",
|
||||||
|
args: &user.ListAuthenticationFactorsRequest{},
|
||||||
|
want: &user.ListAuthenticationFactorsResponse{
|
||||||
|
Result: []*user.AuthFactor{
|
||||||
|
{
|
||||||
|
State: user.AuthFactorState_AUTH_FACTOR_STATE_READY,
|
||||||
|
Type: &user.AuthFactor_OtpEmail{
|
||||||
|
OtpEmail: &user.AuthFactorOTPEmail{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dep: func(args *user.ListAuthenticationFactorsRequest, want *user.ListAuthenticationFactorsResponse) {
|
||||||
|
userWithEmail := Instance.CreateHumanUserVerified(CTX, Instance.DefaultOrg.GetId(), gofakeit.Email(), gofakeit.Phone()).GetUserId()
|
||||||
|
Instance.RegisterUserOTPEmail(CTX, userWithEmail)
|
||||||
|
|
||||||
|
args.UserId = userWithEmail
|
||||||
|
},
|
||||||
|
ctx: CTX,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with not ready u2f",
|
||||||
|
args: &user.ListAuthenticationFactorsRequest{},
|
||||||
|
want: &user.ListAuthenticationFactorsResponse{
|
||||||
|
Result: []*user.AuthFactor{},
|
||||||
|
},
|
||||||
|
dep: func(args *user.ListAuthenticationFactorsRequest, want *user.ListAuthenticationFactorsResponse) {
|
||||||
|
userWithNotReadyU2F := Instance.CreateHumanUser(CTX).GetUserId()
|
||||||
|
_, err := Instance.Client.UserV2.RegisterU2F(CTX, &user.RegisterU2FRequest{
|
||||||
|
UserId: userWithNotReadyU2F,
|
||||||
|
Domain: Instance.Domain,
|
||||||
|
})
|
||||||
|
logging.OnError(err).Panic("Could not register u2f")
|
||||||
|
|
||||||
|
args.UserId = userWithNotReadyU2F
|
||||||
|
},
|
||||||
|
ctx: CTX,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with not ready u2f state filtered",
|
||||||
|
args: &user.ListAuthenticationFactorsRequest{
|
||||||
|
States: []user.AuthFactorState{user.AuthFactorState_AUTH_FACTOR_STATE_NOT_READY},
|
||||||
|
},
|
||||||
|
want: &user.ListAuthenticationFactorsResponse{
|
||||||
|
Result: []*user.AuthFactor{
|
||||||
|
{
|
||||||
|
State: user.AuthFactorState_AUTH_FACTOR_STATE_NOT_READY,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dep: func(args *user.ListAuthenticationFactorsRequest, want *user.ListAuthenticationFactorsResponse) {
|
||||||
|
userWithNotReadyU2F := Instance.CreateHumanUser(CTX).GetUserId()
|
||||||
|
U2FNotReady, err := Instance.Client.UserV2.RegisterU2F(CTX, &user.RegisterU2FRequest{
|
||||||
|
UserId: userWithNotReadyU2F,
|
||||||
|
Domain: Instance.Domain,
|
||||||
|
})
|
||||||
|
logging.OnError(err).Panic("Could not register u2f")
|
||||||
|
|
||||||
|
args.UserId = userWithNotReadyU2F
|
||||||
|
want.Result[0].Type = &user.AuthFactor_U2F{
|
||||||
|
U2F: &user.AuthFactorU2F{
|
||||||
|
Id: U2FNotReady.GetU2FId(),
|
||||||
|
Name: "",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ctx: CTX,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with no userId",
|
||||||
|
args: &user.ListAuthenticationFactorsRequest{
|
||||||
|
UserId: "",
|
||||||
|
},
|
||||||
|
ctx: CTX,
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with no permission",
|
||||||
|
args: &user.ListAuthenticationFactorsRequest{},
|
||||||
|
dep: func(args *user.ListAuthenticationFactorsRequest, want *user.ListAuthenticationFactorsResponse) {
|
||||||
|
userWithTOTP := Instance.CreateHumanUserWithTOTP(CTX, "totp").GetUserId()
|
||||||
|
|
||||||
|
args.UserId = userWithTOTP
|
||||||
|
},
|
||||||
|
ctx: UserCTX,
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "with unknown user",
|
||||||
|
args: &user.ListAuthenticationFactorsRequest{
|
||||||
|
UserId: "unknown",
|
||||||
|
},
|
||||||
|
want: &user.ListAuthenticationFactorsResponse{},
|
||||||
|
ctx: CTX,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if tt.dep != nil {
|
||||||
|
tt.dep(tt.args, tt.want)
|
||||||
|
}
|
||||||
|
|
||||||
|
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(CTX, time.Minute)
|
||||||
|
require.EventuallyWithT(t, func(ttt *assert.CollectT) {
|
||||||
|
got, err := Client.ListAuthenticationFactors(tt.ctx, tt.args)
|
||||||
|
if tt.wantErr {
|
||||||
|
require.Error(ttt, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
require.NoError(ttt, err)
|
||||||
|
|
||||||
|
assert.ElementsMatch(t, tt.want.GetResult(), got.GetResult())
|
||||||
|
}, retryDuration, tick, "timeout waiting for expected auth methods result")
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestServer_CreateInviteCode(t *testing.T) {
|
func TestServer_CreateInviteCode(t *testing.T) {
|
||||||
type args struct {
|
type args struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
|
@ -238,6 +238,8 @@ func userQueryToQuery(query *user.SearchQuery, level uint8) (query.SearchQuery,
|
|||||||
return displayNameQueryToQuery(q.DisplayNameQuery)
|
return displayNameQueryToQuery(q.DisplayNameQuery)
|
||||||
case *user.SearchQuery_EmailQuery:
|
case *user.SearchQuery_EmailQuery:
|
||||||
return emailQueryToQuery(q.EmailQuery)
|
return emailQueryToQuery(q.EmailQuery)
|
||||||
|
case *user.SearchQuery_PhoneQuery:
|
||||||
|
return phoneQueryToQuery(q.PhoneQuery)
|
||||||
case *user.SearchQuery_StateQuery:
|
case *user.SearchQuery_StateQuery:
|
||||||
return stateQueryToQuery(q.StateQuery)
|
return stateQueryToQuery(q.StateQuery)
|
||||||
case *user.SearchQuery_TypeQuery:
|
case *user.SearchQuery_TypeQuery:
|
||||||
@ -285,6 +287,10 @@ func emailQueryToQuery(q *user.EmailQuery) (query.SearchQuery, error) {
|
|||||||
return query.NewUserEmailSearchQuery(q.EmailAddress, object.TextMethodToQuery(q.Method))
|
return query.NewUserEmailSearchQuery(q.EmailAddress, object.TextMethodToQuery(q.Method))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func phoneQueryToQuery(q *user.PhoneQuery) (query.SearchQuery, error) {
|
||||||
|
return query.NewUserPhoneSearchQuery(q.Number, object.TextMethodToQuery(q.Method))
|
||||||
|
}
|
||||||
|
|
||||||
func stateQueryToQuery(q *user.StateQuery) (query.SearchQuery, error) {
|
func stateQueryToQuery(q *user.StateQuery) (query.SearchQuery, error) {
|
||||||
return query.NewUserStateSearchQuery(int32(q.State))
|
return query.NewUserStateSearchQuery(int32(q.State))
|
||||||
}
|
}
|
||||||
|
@ -597,6 +597,39 @@ func (s *Server) ListAuthenticationMethodTypes(ctx context.Context, req *user.Li
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Server) ListAuthenticationFactors(ctx context.Context, req *user.ListAuthenticationFactorsRequest) (*user.ListAuthenticationFactorsResponse, error) {
|
||||||
|
query := new(query.UserAuthMethodSearchQueries)
|
||||||
|
|
||||||
|
if err := query.AppendUserIDQuery(req.UserId); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
authMethodsType := []domain.UserAuthMethodType{domain.UserAuthMethodTypeU2F, domain.UserAuthMethodTypeTOTP, domain.UserAuthMethodTypeOTPSMS, domain.UserAuthMethodTypeOTPEmail}
|
||||||
|
if len(req.GetAuthFactors()) > 0 {
|
||||||
|
authMethodsType = object.AuthFactorsToPb(req.GetAuthFactors())
|
||||||
|
}
|
||||||
|
if err := query.AppendAuthMethodsQuery(authMethodsType...); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
states := []domain.MFAState{domain.MFAStateReady}
|
||||||
|
if len(req.GetStates()) > 0 {
|
||||||
|
states = object.AuthFactorStatesToPb(req.GetStates())
|
||||||
|
}
|
||||||
|
if err := query.AppendStatesQuery(states...); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
authMethods, err := s.query.SearchUserAuthMethods(ctx, query, s.checkPermission)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &user.ListAuthenticationFactorsResponse{
|
||||||
|
Result: object.AuthMethodsToPb(authMethods),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func authMethodTypesToPb(methodTypes []domain.UserAuthMethodType) []user.AuthenticationMethodType {
|
func authMethodTypesToPb(methodTypes []domain.UserAuthMethodType) []user.AuthenticationMethodType {
|
||||||
methods := make([]user.AuthenticationMethodType, len(methodTypes))
|
methods := make([]user.AuthenticationMethodType, len(methodTypes))
|
||||||
for i, method := range methodTypes {
|
for i, method := range methodTypes {
|
||||||
|
@ -147,7 +147,7 @@ func TestServer_SetEmail(t *testing.T) {
|
|||||||
|
|
||||||
func TestServer_ResendEmailCode(t *testing.T) {
|
func TestServer_ResendEmailCode(t *testing.T) {
|
||||||
userID := Instance.CreateHumanUser(CTX).GetUserId()
|
userID := Instance.CreateHumanUser(CTX).GetUserId()
|
||||||
verifiedUserID := Instance.CreateHumanUserVerified(CTX, Instance.DefaultOrg.Id, gofakeit.Email()).GetUserId()
|
verifiedUserID := Instance.CreateHumanUserVerified(CTX, Instance.DefaultOrg.Id, gofakeit.Email(), gofakeit.Phone()).GetUserId()
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
@ -125,7 +125,7 @@ func TestServer_SetPhone(t *testing.T) {
|
|||||||
|
|
||||||
func TestServer_ResendPhoneCode(t *testing.T) {
|
func TestServer_ResendPhoneCode(t *testing.T) {
|
||||||
userID := Instance.CreateHumanUser(CTX).GetUserId()
|
userID := Instance.CreateHumanUser(CTX).GetUserId()
|
||||||
verifiedUserID := Instance.CreateHumanUserVerified(CTX, Instance.DefaultOrg.Id, gofakeit.Email()).GetUserId()
|
verifiedUserID := Instance.CreateHumanUserVerified(CTX, Instance.DefaultOrg.Id, gofakeit.Email(), gofakeit.Phone()).GetUserId()
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
@ -5,6 +5,7 @@ package user_test
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"slices"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -33,7 +34,7 @@ func TestServer_GetUserByID(t *testing.T) {
|
|||||||
type args struct {
|
type args struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
req *user.GetUserByIDRequest
|
req *user.GetUserByIDRequest
|
||||||
dep func(ctx context.Context, username string, request *user.GetUserByIDRequest) (*userAttr, error)
|
dep func(ctx context.Context, request *user.GetUserByIDRequest) *userAttr
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@ -48,8 +49,8 @@ func TestServer_GetUserByID(t *testing.T) {
|
|||||||
&user.GetUserByIDRequest{
|
&user.GetUserByIDRequest{
|
||||||
UserId: "",
|
UserId: "",
|
||||||
},
|
},
|
||||||
func(ctx context.Context, username string, request *user.GetUserByIDRequest) (*userAttr, error) {
|
func(ctx context.Context, request *user.GetUserByIDRequest) *userAttr {
|
||||||
return nil, nil
|
return nil
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
@ -61,8 +62,8 @@ func TestServer_GetUserByID(t *testing.T) {
|
|||||||
&user.GetUserByIDRequest{
|
&user.GetUserByIDRequest{
|
||||||
UserId: "unknown",
|
UserId: "unknown",
|
||||||
},
|
},
|
||||||
func(ctx context.Context, username string, request *user.GetUserByIDRequest) (*userAttr, error) {
|
func(ctx context.Context, request *user.GetUserByIDRequest) *userAttr {
|
||||||
return nil, nil
|
return nil
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
@ -72,10 +73,10 @@ func TestServer_GetUserByID(t *testing.T) {
|
|||||||
args: args{
|
args: args{
|
||||||
IamCTX,
|
IamCTX,
|
||||||
&user.GetUserByIDRequest{},
|
&user.GetUserByIDRequest{},
|
||||||
func(ctx context.Context, username string, request *user.GetUserByIDRequest) (*userAttr, error) {
|
func(ctx context.Context, request *user.GetUserByIDRequest) *userAttr {
|
||||||
resp := Instance.CreateHumanUserVerified(ctx, orgResp.OrganizationId, username)
|
info := createUser(ctx, orgResp.OrganizationId, false)
|
||||||
request.UserId = resp.GetUserId()
|
request.UserId = info.UserID
|
||||||
return &userAttr{resp.GetUserId(), username, nil, resp.GetDetails()}, nil
|
return &info
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
want: &user.GetUserByIDResponse{
|
want: &user.GetUserByIDResponse{
|
||||||
@ -99,7 +100,6 @@ func TestServer_GetUserByID(t *testing.T) {
|
|||||||
IsVerified: true,
|
IsVerified: true,
|
||||||
},
|
},
|
||||||
Phone: &user.HumanPhone{
|
Phone: &user.HumanPhone{
|
||||||
Phone: "+41791234567",
|
|
||||||
IsVerified: true,
|
IsVerified: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -116,11 +116,10 @@ func TestServer_GetUserByID(t *testing.T) {
|
|||||||
args: args{
|
args: args{
|
||||||
IamCTX,
|
IamCTX,
|
||||||
&user.GetUserByIDRequest{},
|
&user.GetUserByIDRequest{},
|
||||||
func(ctx context.Context, username string, request *user.GetUserByIDRequest) (*userAttr, error) {
|
func(ctx context.Context, request *user.GetUserByIDRequest) *userAttr {
|
||||||
resp := Instance.CreateHumanUserVerified(ctx, orgResp.OrganizationId, username)
|
info := createUser(ctx, orgResp.OrganizationId, true)
|
||||||
request.UserId = resp.GetUserId()
|
request.UserId = info.UserID
|
||||||
details := Instance.SetUserPassword(ctx, resp.GetUserId(), integration.UserPassword, true)
|
return &info
|
||||||
return &userAttr{resp.GetUserId(), username, details.GetChangeDate(), resp.GetDetails()}, nil
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
want: &user.GetUserByIDResponse{
|
want: &user.GetUserByIDResponse{
|
||||||
@ -144,7 +143,6 @@ func TestServer_GetUserByID(t *testing.T) {
|
|||||||
IsVerified: true,
|
IsVerified: true,
|
||||||
},
|
},
|
||||||
Phone: &user.HumanPhone{
|
Phone: &user.HumanPhone{
|
||||||
Phone: "+41791234567",
|
|
||||||
IsVerified: true,
|
IsVerified: true,
|
||||||
},
|
},
|
||||||
PasswordChangeRequired: true,
|
PasswordChangeRequired: true,
|
||||||
@ -161,9 +159,7 @@ func TestServer_GetUserByID(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
username := gofakeit.Email()
|
userAttr := tt.args.dep(IamCTX, tt.args.req)
|
||||||
userAttr, err := tt.args.dep(tt.args.ctx, username, tt.args.req)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(tt.args.ctx, time.Minute)
|
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(tt.args.ctx, time.Minute)
|
||||||
require.EventuallyWithT(t, func(ttt *assert.CollectT) {
|
require.EventuallyWithT(t, func(ttt *assert.CollectT) {
|
||||||
@ -183,6 +179,7 @@ func TestServer_GetUserByID(t *testing.T) {
|
|||||||
tt.want.User.LoginNames = []string{userAttr.Username}
|
tt.want.User.LoginNames = []string{userAttr.Username}
|
||||||
if human := tt.want.User.GetHuman(); human != nil {
|
if human := tt.want.User.GetHuman(); human != nil {
|
||||||
human.Email.Email = userAttr.Username
|
human.Email.Email = userAttr.Username
|
||||||
|
human.Phone.Phone = userAttr.Phone
|
||||||
if tt.want.User.GetHuman().GetPasswordChanged() != nil {
|
if tt.want.User.GetHuman().GetPasswordChanged() != nil {
|
||||||
human.PasswordChanged = userAttr.Changed
|
human.PasswordChanged = userAttr.Changed
|
||||||
}
|
}
|
||||||
@ -335,21 +332,60 @@ func TestServer_GetUserByID_Permission(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type userAttrs []userAttr
|
||||||
|
|
||||||
|
func (u userAttrs) userIDs() []string {
|
||||||
|
ids := make([]string, len(u))
|
||||||
|
for i := range u {
|
||||||
|
ids[i] = u[i].UserID
|
||||||
|
}
|
||||||
|
return ids
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u userAttrs) emails() []string {
|
||||||
|
emails := make([]string, len(u))
|
||||||
|
for i := range u {
|
||||||
|
emails[i] = u[i].Username
|
||||||
|
}
|
||||||
|
return emails
|
||||||
|
}
|
||||||
|
|
||||||
type userAttr struct {
|
type userAttr struct {
|
||||||
UserID string
|
UserID string
|
||||||
Username string
|
Username string
|
||||||
|
Phone string
|
||||||
Changed *timestamppb.Timestamp
|
Changed *timestamppb.Timestamp
|
||||||
Details *object.Details
|
Details *object.Details
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func createUsers(ctx context.Context, orgID string, count int, passwordChangeRequired bool) userAttrs {
|
||||||
|
infos := make([]userAttr, count)
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
infos[i] = createUser(ctx, orgID, passwordChangeRequired)
|
||||||
|
}
|
||||||
|
slices.Reverse(infos)
|
||||||
|
return infos
|
||||||
|
}
|
||||||
|
|
||||||
|
func createUser(ctx context.Context, orgID string, passwordChangeRequired bool) userAttr {
|
||||||
|
username := gofakeit.Email()
|
||||||
|
// used as default country prefix
|
||||||
|
phone := "+41" + gofakeit.Phone()
|
||||||
|
resp := Instance.CreateHumanUserVerified(ctx, orgID, username, phone)
|
||||||
|
info := userAttr{resp.GetUserId(), username, phone, nil, resp.GetDetails()}
|
||||||
|
if passwordChangeRequired {
|
||||||
|
details := Instance.SetUserPassword(ctx, resp.GetUserId(), integration.UserPassword, true)
|
||||||
|
info.Changed = details.GetChangeDate()
|
||||||
|
}
|
||||||
|
return info
|
||||||
|
}
|
||||||
|
|
||||||
func TestServer_ListUsers(t *testing.T) {
|
func TestServer_ListUsers(t *testing.T) {
|
||||||
orgResp := Instance.CreateOrganization(IamCTX, fmt.Sprintf("ListUsersOrg-%s", gofakeit.AppName()), gofakeit.Email())
|
orgResp := Instance.CreateOrganization(IamCTX, fmt.Sprintf("ListUsersOrg-%s", gofakeit.AppName()), gofakeit.Email())
|
||||||
userResp := Instance.CreateHumanUserVerified(IamCTX, orgResp.OrganizationId, gofakeit.Email())
|
|
||||||
type args struct {
|
type args struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
count int
|
req *user.ListUsersRequest
|
||||||
req *user.ListUsersRequest
|
dep func(ctx context.Context, request *user.ListUsersRequest) userAttrs
|
||||||
dep func(ctx context.Context, usernames []string, request *user.ListUsersRequest) ([]userAttr, error)
|
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@ -361,11 +397,11 @@ func TestServer_ListUsers(t *testing.T) {
|
|||||||
name: "list user by id, no permission",
|
name: "list user by id, no permission",
|
||||||
args: args{
|
args: args{
|
||||||
UserCTX,
|
UserCTX,
|
||||||
0,
|
|
||||||
&user.ListUsersRequest{},
|
&user.ListUsersRequest{},
|
||||||
func(ctx context.Context, usernames []string, request *user.ListUsersRequest) ([]userAttr, error) {
|
func(ctx context.Context, request *user.ListUsersRequest) userAttrs {
|
||||||
request.Queries = append(request.Queries, InUserIDsQuery([]string{userResp.UserId}))
|
info := createUser(ctx, orgResp.OrganizationId, false)
|
||||||
return []userAttr{}, nil
|
request.Queries = append(request.Queries, InUserIDsQuery([]string{info.UserID}))
|
||||||
|
return []userAttr{}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
want: &user.ListUsersResponse{
|
want: &user.ListUsersResponse{
|
||||||
@ -381,22 +417,15 @@ func TestServer_ListUsers(t *testing.T) {
|
|||||||
name: "list user by id, ok",
|
name: "list user by id, ok",
|
||||||
args: args{
|
args: args{
|
||||||
IamCTX,
|
IamCTX,
|
||||||
1,
|
|
||||||
&user.ListUsersRequest{
|
&user.ListUsersRequest{
|
||||||
Queries: []*user.SearchQuery{
|
Queries: []*user.SearchQuery{
|
||||||
OrganizationIdQuery(orgResp.OrganizationId),
|
OrganizationIdQuery(orgResp.OrganizationId),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
func(ctx context.Context, usernames []string, request *user.ListUsersRequest) ([]userAttr, error) {
|
func(ctx context.Context, request *user.ListUsersRequest) userAttrs {
|
||||||
infos := make([]userAttr, len(usernames))
|
info := createUser(ctx, orgResp.OrganizationId, false)
|
||||||
userIDs := make([]string, len(usernames))
|
request.Queries = append(request.Queries, InUserIDsQuery([]string{info.UserID}))
|
||||||
for i, username := range usernames {
|
return []userAttr{info}
|
||||||
resp := Instance.CreateHumanUserVerified(ctx, orgResp.OrganizationId, username)
|
|
||||||
userIDs[i] = resp.GetUserId()
|
|
||||||
infos[i] = userAttr{resp.GetUserId(), username, nil, resp.GetDetails()}
|
|
||||||
}
|
|
||||||
request.Queries = append(request.Queries, InUserIDsQuery(userIDs))
|
|
||||||
return infos, nil
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
want: &user.ListUsersResponse{
|
want: &user.ListUsersResponse{
|
||||||
@ -422,7 +451,6 @@ func TestServer_ListUsers(t *testing.T) {
|
|||||||
IsVerified: true,
|
IsVerified: true,
|
||||||
},
|
},
|
||||||
Phone: &user.HumanPhone{
|
Phone: &user.HumanPhone{
|
||||||
Phone: "+41791234567",
|
|
||||||
IsVerified: true,
|
IsVerified: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -435,23 +463,15 @@ func TestServer_ListUsers(t *testing.T) {
|
|||||||
name: "list user by id, passwordChangeRequired, ok",
|
name: "list user by id, passwordChangeRequired, ok",
|
||||||
args: args{
|
args: args{
|
||||||
IamCTX,
|
IamCTX,
|
||||||
1,
|
|
||||||
&user.ListUsersRequest{
|
&user.ListUsersRequest{
|
||||||
Queries: []*user.SearchQuery{
|
Queries: []*user.SearchQuery{
|
||||||
OrganizationIdQuery(orgResp.OrganizationId),
|
OrganizationIdQuery(orgResp.OrganizationId),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
func(ctx context.Context, usernames []string, request *user.ListUsersRequest) ([]userAttr, error) {
|
func(ctx context.Context, request *user.ListUsersRequest) userAttrs {
|
||||||
infos := make([]userAttr, len(usernames))
|
info := createUser(ctx, orgResp.OrganizationId, true)
|
||||||
userIDs := make([]string, len(usernames))
|
request.Queries = append(request.Queries, InUserIDsQuery([]string{info.UserID}))
|
||||||
for i, username := range usernames {
|
return []userAttr{info}
|
||||||
resp := Instance.CreateHumanUserVerified(ctx, orgResp.OrganizationId, username)
|
|
||||||
userIDs[i] = resp.GetUserId()
|
|
||||||
details := Instance.SetUserPassword(ctx, resp.GetUserId(), integration.UserPassword, true)
|
|
||||||
infos[i] = userAttr{resp.GetUserId(), username, details.GetChangeDate(), resp.GetDetails()}
|
|
||||||
}
|
|
||||||
request.Queries = append(request.Queries, InUserIDsQuery(userIDs))
|
|
||||||
return infos, nil
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
want: &user.ListUsersResponse{
|
want: &user.ListUsersResponse{
|
||||||
@ -477,7 +497,6 @@ func TestServer_ListUsers(t *testing.T) {
|
|||||||
IsVerified: true,
|
IsVerified: true,
|
||||||
},
|
},
|
||||||
Phone: &user.HumanPhone{
|
Phone: &user.HumanPhone{
|
||||||
Phone: "+41791234567",
|
|
||||||
IsVerified: true,
|
IsVerified: true,
|
||||||
},
|
},
|
||||||
PasswordChangeRequired: true,
|
PasswordChangeRequired: true,
|
||||||
@ -492,22 +511,15 @@ func TestServer_ListUsers(t *testing.T) {
|
|||||||
name: "list user by id multiple, ok",
|
name: "list user by id multiple, ok",
|
||||||
args: args{
|
args: args{
|
||||||
IamCTX,
|
IamCTX,
|
||||||
3,
|
|
||||||
&user.ListUsersRequest{
|
&user.ListUsersRequest{
|
||||||
Queries: []*user.SearchQuery{
|
Queries: []*user.SearchQuery{
|
||||||
OrganizationIdQuery(orgResp.OrganizationId),
|
OrganizationIdQuery(orgResp.OrganizationId),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
func(ctx context.Context, usernames []string, request *user.ListUsersRequest) ([]userAttr, error) {
|
func(ctx context.Context, request *user.ListUsersRequest) userAttrs {
|
||||||
infos := make([]userAttr, len(usernames))
|
infos := createUsers(ctx, orgResp.OrganizationId, 3, false)
|
||||||
userIDs := make([]string, len(usernames))
|
request.Queries = append(request.Queries, InUserIDsQuery(infos.userIDs()))
|
||||||
for i, username := range usernames {
|
return infos
|
||||||
resp := Instance.CreateHumanUserVerified(ctx, orgResp.OrganizationId, username)
|
|
||||||
userIDs[i] = resp.GetUserId()
|
|
||||||
infos[i] = userAttr{resp.GetUserId(), username, nil, resp.GetDetails()}
|
|
||||||
}
|
|
||||||
request.Queries = append(request.Queries, InUserIDsQuery(userIDs))
|
|
||||||
return infos, nil
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
want: &user.ListUsersResponse{
|
want: &user.ListUsersResponse{
|
||||||
@ -533,7 +545,6 @@ func TestServer_ListUsers(t *testing.T) {
|
|||||||
IsVerified: true,
|
IsVerified: true,
|
||||||
},
|
},
|
||||||
Phone: &user.HumanPhone{
|
Phone: &user.HumanPhone{
|
||||||
Phone: "+41791234567",
|
|
||||||
IsVerified: true,
|
IsVerified: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -554,7 +565,6 @@ func TestServer_ListUsers(t *testing.T) {
|
|||||||
IsVerified: true,
|
IsVerified: true,
|
||||||
},
|
},
|
||||||
Phone: &user.HumanPhone{
|
Phone: &user.HumanPhone{
|
||||||
Phone: "+41791234567",
|
|
||||||
IsVerified: true,
|
IsVerified: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -575,7 +585,6 @@ func TestServer_ListUsers(t *testing.T) {
|
|||||||
IsVerified: true,
|
IsVerified: true,
|
||||||
},
|
},
|
||||||
Phone: &user.HumanPhone{
|
Phone: &user.HumanPhone{
|
||||||
Phone: "+41791234567",
|
|
||||||
IsVerified: true,
|
IsVerified: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -588,22 +597,15 @@ func TestServer_ListUsers(t *testing.T) {
|
|||||||
name: "list user by username, ok",
|
name: "list user by username, ok",
|
||||||
args: args{
|
args: args{
|
||||||
IamCTX,
|
IamCTX,
|
||||||
1,
|
|
||||||
&user.ListUsersRequest{
|
&user.ListUsersRequest{
|
||||||
Queries: []*user.SearchQuery{
|
Queries: []*user.SearchQuery{
|
||||||
OrganizationIdQuery(orgResp.OrganizationId),
|
OrganizationIdQuery(orgResp.OrganizationId),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
func(ctx context.Context, usernames []string, request *user.ListUsersRequest) ([]userAttr, error) {
|
func(ctx context.Context, request *user.ListUsersRequest) userAttrs {
|
||||||
infos := make([]userAttr, len(usernames))
|
info := createUser(ctx, orgResp.OrganizationId, false)
|
||||||
userIDs := make([]string, len(usernames))
|
request.Queries = append(request.Queries, UsernameQuery(info.Username))
|
||||||
for i, username := range usernames {
|
return []userAttr{info}
|
||||||
resp := Instance.CreateHumanUserVerified(ctx, orgResp.OrganizationId, username)
|
|
||||||
userIDs[i] = resp.GetUserId()
|
|
||||||
infos[i] = userAttr{resp.GetUserId(), username, nil, resp.GetDetails()}
|
|
||||||
request.Queries = append(request.Queries, UsernameQuery(username))
|
|
||||||
}
|
|
||||||
return infos, nil
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
want: &user.ListUsersResponse{
|
want: &user.ListUsersResponse{
|
||||||
@ -629,7 +631,6 @@ func TestServer_ListUsers(t *testing.T) {
|
|||||||
IsVerified: true,
|
IsVerified: true,
|
||||||
},
|
},
|
||||||
Phone: &user.HumanPhone{
|
Phone: &user.HumanPhone{
|
||||||
Phone: "+41791234567",
|
|
||||||
IsVerified: true,
|
IsVerified: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -642,20 +643,15 @@ func TestServer_ListUsers(t *testing.T) {
|
|||||||
name: "list user in emails, ok",
|
name: "list user in emails, ok",
|
||||||
args: args{
|
args: args{
|
||||||
IamCTX,
|
IamCTX,
|
||||||
1,
|
|
||||||
&user.ListUsersRequest{
|
&user.ListUsersRequest{
|
||||||
Queries: []*user.SearchQuery{
|
Queries: []*user.SearchQuery{
|
||||||
OrganizationIdQuery(orgResp.OrganizationId),
|
OrganizationIdQuery(orgResp.OrganizationId),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
func(ctx context.Context, usernames []string, request *user.ListUsersRequest) ([]userAttr, error) {
|
func(ctx context.Context, request *user.ListUsersRequest) userAttrs {
|
||||||
infos := make([]userAttr, len(usernames))
|
info := createUser(ctx, orgResp.OrganizationId, false)
|
||||||
for i, username := range usernames {
|
request.Queries = append(request.Queries, InUserEmailsQuery([]string{info.Username}))
|
||||||
resp := Instance.CreateHumanUserVerified(ctx, orgResp.OrganizationId, username)
|
return []userAttr{info}
|
||||||
infos[i] = userAttr{resp.GetUserId(), username, nil, resp.GetDetails()}
|
|
||||||
}
|
|
||||||
request.Queries = append(request.Queries, InUserEmailsQuery(usernames))
|
|
||||||
return infos, nil
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
want: &user.ListUsersResponse{
|
want: &user.ListUsersResponse{
|
||||||
@ -681,7 +677,6 @@ func TestServer_ListUsers(t *testing.T) {
|
|||||||
IsVerified: true,
|
IsVerified: true,
|
||||||
},
|
},
|
||||||
Phone: &user.HumanPhone{
|
Phone: &user.HumanPhone{
|
||||||
Phone: "+41791234567",
|
|
||||||
IsVerified: true,
|
IsVerified: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -694,20 +689,15 @@ func TestServer_ListUsers(t *testing.T) {
|
|||||||
name: "list user in emails multiple, ok",
|
name: "list user in emails multiple, ok",
|
||||||
args: args{
|
args: args{
|
||||||
IamCTX,
|
IamCTX,
|
||||||
3,
|
|
||||||
&user.ListUsersRequest{
|
&user.ListUsersRequest{
|
||||||
Queries: []*user.SearchQuery{
|
Queries: []*user.SearchQuery{
|
||||||
OrganizationIdQuery(orgResp.OrganizationId),
|
OrganizationIdQuery(orgResp.OrganizationId),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
func(ctx context.Context, usernames []string, request *user.ListUsersRequest) ([]userAttr, error) {
|
func(ctx context.Context, request *user.ListUsersRequest) userAttrs {
|
||||||
infos := make([]userAttr, len(usernames))
|
infos := createUsers(ctx, orgResp.OrganizationId, 3, false)
|
||||||
for i, username := range usernames {
|
request.Queries = append(request.Queries, InUserEmailsQuery(infos.emails()))
|
||||||
resp := Instance.CreateHumanUserVerified(ctx, orgResp.OrganizationId, username)
|
return infos
|
||||||
infos[i] = userAttr{resp.GetUserId(), username, nil, resp.GetDetails()}
|
|
||||||
}
|
|
||||||
request.Queries = append(request.Queries, InUserEmailsQuery(usernames))
|
|
||||||
return infos, nil
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
want: &user.ListUsersResponse{
|
want: &user.ListUsersResponse{
|
||||||
@ -733,7 +723,6 @@ func TestServer_ListUsers(t *testing.T) {
|
|||||||
IsVerified: true,
|
IsVerified: true,
|
||||||
},
|
},
|
||||||
Phone: &user.HumanPhone{
|
Phone: &user.HumanPhone{
|
||||||
Phone: "+41791234567",
|
|
||||||
IsVerified: true,
|
IsVerified: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -754,7 +743,6 @@ func TestServer_ListUsers(t *testing.T) {
|
|||||||
IsVerified: true,
|
IsVerified: true,
|
||||||
},
|
},
|
||||||
Phone: &user.HumanPhone{
|
Phone: &user.HumanPhone{
|
||||||
Phone: "+41791234567",
|
|
||||||
IsVerified: true,
|
IsVerified: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -775,7 +763,6 @@ func TestServer_ListUsers(t *testing.T) {
|
|||||||
IsVerified: true,
|
IsVerified: true,
|
||||||
},
|
},
|
||||||
Phone: &user.HumanPhone{
|
Phone: &user.HumanPhone{
|
||||||
Phone: "+41791234567",
|
|
||||||
IsVerified: true,
|
IsVerified: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -788,14 +775,13 @@ func TestServer_ListUsers(t *testing.T) {
|
|||||||
name: "list user in emails no found, ok",
|
name: "list user in emails no found, ok",
|
||||||
args: args{
|
args: args{
|
||||||
IamCTX,
|
IamCTX,
|
||||||
3,
|
|
||||||
&user.ListUsersRequest{Queries: []*user.SearchQuery{
|
&user.ListUsersRequest{Queries: []*user.SearchQuery{
|
||||||
OrganizationIdQuery(orgResp.OrganizationId),
|
OrganizationIdQuery(orgResp.OrganizationId),
|
||||||
InUserEmailsQuery([]string{"notfound"}),
|
InUserEmailsQuery([]string{"notfound"}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
func(ctx context.Context, usernames []string, request *user.ListUsersRequest) ([]userAttr, error) {
|
func(ctx context.Context, request *user.ListUsersRequest) userAttrs {
|
||||||
return []userAttr{}, nil
|
return []userAttr{}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
want: &user.ListUsersResponse{
|
want: &user.ListUsersResponse{
|
||||||
@ -807,23 +793,64 @@ func TestServer_ListUsers(t *testing.T) {
|
|||||||
Result: []*user.User{},
|
Result: []*user.User{},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "list user phone, ok",
|
||||||
|
args: args{
|
||||||
|
IamCTX,
|
||||||
|
&user.ListUsersRequest{
|
||||||
|
Queries: []*user.SearchQuery{
|
||||||
|
OrganizationIdQuery(orgResp.OrganizationId),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
func(ctx context.Context, request *user.ListUsersRequest) userAttrs {
|
||||||
|
info := createUser(ctx, orgResp.OrganizationId, false)
|
||||||
|
request.Queries = append(request.Queries, PhoneQuery(info.Phone))
|
||||||
|
return []userAttr{info}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: &user.ListUsersResponse{
|
||||||
|
Details: &object_v2beta.ListDetails{
|
||||||
|
TotalResult: 1,
|
||||||
|
Timestamp: timestamppb.Now(),
|
||||||
|
},
|
||||||
|
SortingColumn: 0,
|
||||||
|
Result: []*user.User{
|
||||||
|
{
|
||||||
|
State: user.UserState_USER_STATE_ACTIVE,
|
||||||
|
Type: &user.User_Human{
|
||||||
|
Human: &user.HumanUser{
|
||||||
|
Profile: &user.HumanProfile{
|
||||||
|
GivenName: "Mickey",
|
||||||
|
FamilyName: "Mouse",
|
||||||
|
NickName: gu.Ptr("Mickey"),
|
||||||
|
DisplayName: gu.Ptr("Mickey Mouse"),
|
||||||
|
PreferredLanguage: gu.Ptr("nl"),
|
||||||
|
Gender: user.Gender_GENDER_MALE.Enum(),
|
||||||
|
},
|
||||||
|
Email: &user.HumanEmail{
|
||||||
|
IsVerified: true,
|
||||||
|
},
|
||||||
|
Phone: &user.HumanPhone{
|
||||||
|
IsVerified: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "list user resourceowner multiple, ok",
|
name: "list user resourceowner multiple, ok",
|
||||||
args: args{
|
args: args{
|
||||||
IamCTX,
|
IamCTX,
|
||||||
3,
|
|
||||||
&user.ListUsersRequest{},
|
&user.ListUsersRequest{},
|
||||||
func(ctx context.Context, usernames []string, request *user.ListUsersRequest) ([]userAttr, error) {
|
func(ctx context.Context, request *user.ListUsersRequest) userAttrs {
|
||||||
orgResp := Instance.CreateOrganization(ctx, fmt.Sprintf("ListUsersResourceowner-%s", gofakeit.AppName()), gofakeit.Email())
|
orgResp := Instance.CreateOrganization(ctx, fmt.Sprintf("ListUsersResourceowner-%s", gofakeit.AppName()), gofakeit.Email())
|
||||||
|
|
||||||
infos := make([]userAttr, len(usernames))
|
infos := createUsers(ctx, orgResp.OrganizationId, 3, false)
|
||||||
for i, username := range usernames {
|
|
||||||
resp := Instance.CreateHumanUserVerified(ctx, orgResp.OrganizationId, username)
|
|
||||||
infos[i] = userAttr{resp.GetUserId(), username, nil, resp.GetDetails()}
|
|
||||||
}
|
|
||||||
request.Queries = append(request.Queries, OrganizationIdQuery(orgResp.OrganizationId))
|
request.Queries = append(request.Queries, OrganizationIdQuery(orgResp.OrganizationId))
|
||||||
request.Queries = append(request.Queries, InUserEmailsQuery(usernames))
|
request.Queries = append(request.Queries, InUserEmailsQuery(infos.emails()))
|
||||||
return infos, nil
|
return infos
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
want: &user.ListUsersResponse{
|
want: &user.ListUsersResponse{
|
||||||
@ -849,7 +876,6 @@ func TestServer_ListUsers(t *testing.T) {
|
|||||||
IsVerified: true,
|
IsVerified: true,
|
||||||
},
|
},
|
||||||
Phone: &user.HumanPhone{
|
Phone: &user.HumanPhone{
|
||||||
Phone: "+41791234567",
|
|
||||||
IsVerified: true,
|
IsVerified: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -870,7 +896,6 @@ func TestServer_ListUsers(t *testing.T) {
|
|||||||
IsVerified: true,
|
IsVerified: true,
|
||||||
},
|
},
|
||||||
Phone: &user.HumanPhone{
|
Phone: &user.HumanPhone{
|
||||||
Phone: "+41791234567",
|
|
||||||
IsVerified: true,
|
IsVerified: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -891,7 +916,6 @@ func TestServer_ListUsers(t *testing.T) {
|
|||||||
IsVerified: true,
|
IsVerified: true,
|
||||||
},
|
},
|
||||||
Phone: &user.HumanPhone{
|
Phone: &user.HumanPhone{
|
||||||
Phone: "+41791234567",
|
|
||||||
IsVerified: true,
|
IsVerified: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -903,12 +927,7 @@ func TestServer_ListUsers(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
usernames := make([]string, tt.args.count)
|
infos := tt.args.dep(IamCTX, tt.args.req)
|
||||||
for i := 0; i < tt.args.count; i++ {
|
|
||||||
usernames[i] = gofakeit.Email()
|
|
||||||
}
|
|
||||||
infos, err := tt.args.dep(tt.args.ctx, usernames, tt.args.req)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(tt.args.ctx, time.Minute)
|
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(tt.args.ctx, time.Minute)
|
||||||
require.EventuallyWithT(t, func(ttt *assert.CollectT) {
|
require.EventuallyWithT(t, func(ttt *assert.CollectT) {
|
||||||
@ -934,6 +953,7 @@ func TestServer_ListUsers(t *testing.T) {
|
|||||||
tt.want.Result[i].LoginNames = []string{infos[i].Username}
|
tt.want.Result[i].LoginNames = []string{infos[i].Username}
|
||||||
if human := tt.want.Result[i].GetHuman(); human != nil {
|
if human := tt.want.Result[i].GetHuman(); human != nil {
|
||||||
human.Email.Email = infos[i].Username
|
human.Email.Email = infos[i].Username
|
||||||
|
human.Phone.Phone = infos[i].Phone
|
||||||
if tt.want.Result[i].GetHuman().GetPasswordChanged() != nil {
|
if tt.want.Result[i].GetHuman().GetPasswordChanged() != nil {
|
||||||
human.PasswordChanged = infos[i].Changed
|
human.PasswordChanged = infos[i].Changed
|
||||||
}
|
}
|
||||||
@ -941,7 +961,7 @@ func TestServer_ListUsers(t *testing.T) {
|
|||||||
tt.want.Result[i].Details = detailsV2ToV2beta(infos[i].Details)
|
tt.want.Result[i].Details = detailsV2ToV2beta(infos[i].Details)
|
||||||
}
|
}
|
||||||
for i := range tt.want.Result {
|
for i := range tt.want.Result {
|
||||||
assert.Contains(ttt, got.Result, tt.want.Result[i])
|
assert.EqualExportedValues(ttt, got.Result[i], tt.want.Result[i])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
integration.AssertListDetails(ttt, tt.want, got)
|
integration.AssertListDetails(ttt, tt.want, got)
|
||||||
@ -968,6 +988,15 @@ func InUserEmailsQuery(emails []string) *user.SearchQuery {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func PhoneQuery(number string) *user.SearchQuery {
|
||||||
|
return &user.SearchQuery{Query: &user.SearchQuery_PhoneQuery{
|
||||||
|
PhoneQuery: &user.PhoneQuery{
|
||||||
|
Number: number,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func UsernameQuery(username string) *user.SearchQuery {
|
func UsernameQuery(username string) *user.SearchQuery {
|
||||||
return &user.SearchQuery{Query: &user.SearchQuery_UserNameQuery{
|
return &user.SearchQuery{Query: &user.SearchQuery_UserNameQuery{
|
||||||
UserNameQuery: &user.UserNameQuery{
|
UserNameQuery: &user.UserNameQuery{
|
||||||
|
@ -238,6 +238,8 @@ func userQueryToQuery(query *user.SearchQuery, level uint8) (query.SearchQuery,
|
|||||||
return displayNameQueryToQuery(q.DisplayNameQuery)
|
return displayNameQueryToQuery(q.DisplayNameQuery)
|
||||||
case *user.SearchQuery_EmailQuery:
|
case *user.SearchQuery_EmailQuery:
|
||||||
return emailQueryToQuery(q.EmailQuery)
|
return emailQueryToQuery(q.EmailQuery)
|
||||||
|
case *user.SearchQuery_PhoneQuery:
|
||||||
|
return phoneQueryToQuery(q.PhoneQuery)
|
||||||
case *user.SearchQuery_StateQuery:
|
case *user.SearchQuery_StateQuery:
|
||||||
return stateQueryToQuery(q.StateQuery)
|
return stateQueryToQuery(q.StateQuery)
|
||||||
case *user.SearchQuery_TypeQuery:
|
case *user.SearchQuery_TypeQuery:
|
||||||
@ -285,6 +287,10 @@ func emailQueryToQuery(q *user.EmailQuery) (query.SearchQuery, error) {
|
|||||||
return query.NewUserEmailSearchQuery(q.EmailAddress, object.TextMethodToQuery(q.Method))
|
return query.NewUserEmailSearchQuery(q.EmailAddress, object.TextMethodToQuery(q.Method))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func phoneQueryToQuery(q *user.PhoneQuery) (query.SearchQuery, error) {
|
||||||
|
return query.NewUserPhoneSearchQuery(q.Number, object.TextMethodToQuery(q.Method))
|
||||||
|
}
|
||||||
|
|
||||||
func stateQueryToQuery(q *user.StateQuery) (query.SearchQuery, error) {
|
func stateQueryToQuery(q *user.StateQuery) (query.SearchQuery, error) {
|
||||||
return query.NewUserStateSearchQuery(int32(q.State))
|
return query.NewUserStateSearchQuery(int32(q.State))
|
||||||
}
|
}
|
||||||
|
@ -28,6 +28,10 @@ type Config struct {
|
|||||||
ShortCache middleware.CacheConfig
|
ShortCache middleware.CacheConfig
|
||||||
LongCache middleware.CacheConfig
|
LongCache middleware.CacheConfig
|
||||||
InstanceManagementURL string
|
InstanceManagementURL string
|
||||||
|
PostHog struct {
|
||||||
|
Token string
|
||||||
|
URL string
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type spaHandler struct {
|
type spaHandler struct {
|
||||||
@ -117,7 +121,7 @@ func Start(config Config, externalSecure bool, issuer op.IssuerFromRequest, call
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
limited := limitingAccessInterceptor.Limit(w, r)
|
limited := limitingAccessInterceptor.Limit(w, r)
|
||||||
environmentJSON, err := createEnvironmentJSON(url, issuer(r), instance.ConsoleClientID(), customerPortal, instanceMgmtURL, limited)
|
environmentJSON, err := createEnvironmentJSON(url, issuer(r), instance.ConsoleClientID(), customerPortal, instanceMgmtURL, config.PostHog.URL, config.PostHog.Token, limited)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, fmt.Sprintf("unable to marshal env for console: %v", err), http.StatusInternalServerError)
|
http.Error(w, fmt.Sprintf("unable to marshal env for console: %v", err), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
@ -150,13 +154,15 @@ func csp() *middleware.CSP {
|
|||||||
return &csp
|
return &csp
|
||||||
}
|
}
|
||||||
|
|
||||||
func createEnvironmentJSON(api, issuer, clientID, customerPortal, instanceMgmtUrl string, exhausted bool) ([]byte, error) {
|
func createEnvironmentJSON(api, issuer, clientID, customerPortal, instanceMgmtUrl, postHogURL, postHogToken string, exhausted bool) ([]byte, error) {
|
||||||
environment := struct {
|
environment := struct {
|
||||||
API string `json:"api,omitempty"`
|
API string `json:"api,omitempty"`
|
||||||
Issuer string `json:"issuer,omitempty"`
|
Issuer string `json:"issuer,omitempty"`
|
||||||
ClientID string `json:"clientid,omitempty"`
|
ClientID string `json:"clientid,omitempty"`
|
||||||
CustomerPortal string `json:"customer_portal,omitempty"`
|
CustomerPortal string `json:"customer_portal,omitempty"`
|
||||||
InstanceManagementURL string `json:"instance_management_url,omitempty"`
|
InstanceManagementURL string `json:"instance_management_url,omitempty"`
|
||||||
|
PostHogURL string `json:"posthog_url,omitempty"`
|
||||||
|
PostHogToken string `json:"posthog_token,omitempty"`
|
||||||
Exhausted bool `json:"exhausted,omitempty"`
|
Exhausted bool `json:"exhausted,omitempty"`
|
||||||
}{
|
}{
|
||||||
API: api,
|
API: api,
|
||||||
@ -164,6 +170,8 @@ func createEnvironmentJSON(api, issuer, clientID, customerPortal, instanceMgmtUr
|
|||||||
ClientID: clientID,
|
ClientID: clientID,
|
||||||
CustomerPortal: customerPortal,
|
CustomerPortal: customerPortal,
|
||||||
InstanceManagementURL: instanceMgmtUrl,
|
InstanceManagementURL: instanceMgmtUrl,
|
||||||
|
PostHogURL: postHogURL,
|
||||||
|
PostHogToken: postHogToken,
|
||||||
Exhausted: exhausted,
|
Exhausted: exhausted,
|
||||||
}
|
}
|
||||||
return json.Marshal(environment)
|
return json.Marshal(environment)
|
||||||
|
@ -3,6 +3,7 @@ package login
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/crewjam/saml/samlsp"
|
"github.com/crewjam/saml/samlsp"
|
||||||
@ -36,6 +37,9 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
queryIDPConfigID = "idpConfigID"
|
queryIDPConfigID = "idpConfigID"
|
||||||
|
queryState = "state"
|
||||||
|
queryRelayState = "RelayState"
|
||||||
|
queryMethod = "method"
|
||||||
tmplExternalNotFoundOption = "externalnotfoundoption"
|
tmplExternalNotFoundOption = "externalnotfoundoption"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -214,13 +218,36 @@ func (l *Login) handleExternalLoginCallbackForm(w http.ResponseWriter, r *http.R
|
|||||||
l.renderLogin(w, r, nil, err)
|
l.renderLogin(w, r, nil, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
r.Form.Add("Method", http.MethodPost)
|
state := r.Form.Get(queryState)
|
||||||
http.Redirect(w, r, HandlerPrefix+EndpointExternalLoginCallback+"?"+r.Form.Encode(), 302)
|
if state == "" {
|
||||||
|
state = r.Form.Get(queryRelayState)
|
||||||
|
}
|
||||||
|
if state == "" {
|
||||||
|
l.renderLogin(w, r, nil, zerrors.ThrowInvalidArgument(nil, "LOGIN-dsg3f", "Errors.AuthRequest.NotFound"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
l.caches.idpFormCallbacks.Set(r.Context(), &idpFormCallback{
|
||||||
|
InstanceID: authz.GetInstance(r.Context()).InstanceID(),
|
||||||
|
State: state,
|
||||||
|
Form: r.Form,
|
||||||
|
})
|
||||||
|
v := url.Values{}
|
||||||
|
v.Set(queryMethod, http.MethodPost)
|
||||||
|
v.Set(queryState, state)
|
||||||
|
http.Redirect(w, r, HandlerPrefix+EndpointExternalLoginCallback+"?"+v.Encode(), 302)
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleExternalLoginCallback handles the callback from a IDP
|
// handleExternalLoginCallback handles the callback from a IDP
|
||||||
// and tries to extract the user with the provided data
|
// and tries to extract the user with the provided data
|
||||||
func (l *Login) handleExternalLoginCallback(w http.ResponseWriter, r *http.Request) {
|
func (l *Login) handleExternalLoginCallback(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// workaround because of CSRF on external identity provider flows using form_post
|
||||||
|
if r.URL.Query().Get(queryMethod) == http.MethodPost {
|
||||||
|
if err := l.setDataFromFormCallback(r, r.URL.Query().Get(queryState)); err != nil {
|
||||||
|
l.renderLogin(w, r, nil, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
data := new(externalIDPCallbackData)
|
data := new(externalIDPCallbackData)
|
||||||
err := l.getParseData(r, data)
|
err := l.getParseData(r, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -230,11 +257,6 @@ func (l *Login) handleExternalLoginCallback(w http.ResponseWriter, r *http.Reque
|
|||||||
if data.State == "" {
|
if data.State == "" {
|
||||||
data.State = data.RelayState
|
data.State = data.RelayState
|
||||||
}
|
}
|
||||||
// workaround because of CSRF on external identity provider flows
|
|
||||||
if data.Method == http.MethodPost {
|
|
||||||
r.Method = http.MethodPost
|
|
||||||
r.PostForm = r.Form
|
|
||||||
}
|
|
||||||
|
|
||||||
userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context())
|
userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context())
|
||||||
authReq, err := l.authRepo.AuthRequestByID(r.Context(), data.State, userAgentID)
|
authReq, err := l.authRepo.AuthRequestByID(r.Context(), data.State, userAgentID)
|
||||||
@ -345,6 +367,25 @@ func (l *Login) handleExternalLoginCallback(w http.ResponseWriter, r *http.Reque
|
|||||||
l.handleExternalUserAuthenticated(w, r, authReq, identityProvider, session, user, l.renderNextStep)
|
l.handleExternalUserAuthenticated(w, r, authReq, identityProvider, session, user, l.renderNextStep)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (l *Login) setDataFromFormCallback(r *http.Request, state string) error {
|
||||||
|
r.Method = http.MethodPost
|
||||||
|
err := r.ParseForm()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// fallback to the form data in case the request was started before the cache was implemented
|
||||||
|
r.PostForm = r.Form
|
||||||
|
idpCallback, ok := l.caches.idpFormCallbacks.Get(r.Context(), idpFormCallbackIndexRequestID,
|
||||||
|
idpFormCallbackKey(authz.GetInstance(r.Context()).InstanceID(), state))
|
||||||
|
if ok {
|
||||||
|
r.PostForm = idpCallback.Form
|
||||||
|
// We need to set the form as well to make sure the data is parsed correctly.
|
||||||
|
// Form precedes PostForm in the parsing order.
|
||||||
|
r.Form = idpCallback.Form
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (l *Login) tryMigrateExternalUserID(r *http.Request, session idp.Session, authReq *domain.AuthRequest, externalUser *domain.ExternalUser) (previousIDMatched bool, err error) {
|
func (l *Login) tryMigrateExternalUserID(r *http.Request, session idp.Session, authReq *domain.AuthRequest, externalUser *domain.ExternalUser) (previousIDMatched bool, err error) {
|
||||||
migration, ok := session.(idp.SessionSupportsMigration)
|
migration, ok := session.(idp.SessionSupportsMigration)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -3,6 +3,7 @@ package login
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -15,6 +16,8 @@ import (
|
|||||||
_ "github.com/zitadel/zitadel/internal/api/ui/login/statik"
|
_ "github.com/zitadel/zitadel/internal/api/ui/login/statik"
|
||||||
auth_repository "github.com/zitadel/zitadel/internal/auth/repository"
|
auth_repository "github.com/zitadel/zitadel/internal/auth/repository"
|
||||||
"github.com/zitadel/zitadel/internal/auth/repository/eventsourcing"
|
"github.com/zitadel/zitadel/internal/auth/repository/eventsourcing"
|
||||||
|
"github.com/zitadel/zitadel/internal/cache"
|
||||||
|
"github.com/zitadel/zitadel/internal/cache/connector"
|
||||||
"github.com/zitadel/zitadel/internal/command"
|
"github.com/zitadel/zitadel/internal/command"
|
||||||
"github.com/zitadel/zitadel/internal/crypto"
|
"github.com/zitadel/zitadel/internal/crypto"
|
||||||
"github.com/zitadel/zitadel/internal/domain"
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
@ -38,6 +41,7 @@ type Login struct {
|
|||||||
samlAuthCallbackURL func(context.Context, string) string
|
samlAuthCallbackURL func(context.Context, string) string
|
||||||
idpConfigAlg crypto.EncryptionAlgorithm
|
idpConfigAlg crypto.EncryptionAlgorithm
|
||||||
userCodeAlg crypto.EncryptionAlgorithm
|
userCodeAlg crypto.EncryptionAlgorithm
|
||||||
|
caches *Caches
|
||||||
}
|
}
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
@ -74,6 +78,7 @@ func CreateLogin(config Config,
|
|||||||
userCodeAlg crypto.EncryptionAlgorithm,
|
userCodeAlg crypto.EncryptionAlgorithm,
|
||||||
idpConfigAlg crypto.EncryptionAlgorithm,
|
idpConfigAlg crypto.EncryptionAlgorithm,
|
||||||
csrfCookieKey []byte,
|
csrfCookieKey []byte,
|
||||||
|
cacheConnectors connector.Connectors,
|
||||||
) (*Login, error) {
|
) (*Login, error) {
|
||||||
login := &Login{
|
login := &Login{
|
||||||
oidcAuthCallbackURL: oidcAuthCallbackURL,
|
oidcAuthCallbackURL: oidcAuthCallbackURL,
|
||||||
@ -94,6 +99,12 @@ func CreateLogin(config Config,
|
|||||||
login.router = CreateRouter(login, middleware.TelemetryHandler(IgnoreInstanceEndpoints...), oidcInstanceHandler, samlInstanceHandler, csrfInterceptor, cacheInterceptor, security, userAgentCookie, issuerInterceptor, accessHandler)
|
login.router = CreateRouter(login, middleware.TelemetryHandler(IgnoreInstanceEndpoints...), oidcInstanceHandler, samlInstanceHandler, csrfInterceptor, cacheInterceptor, security, userAgentCookie, issuerInterceptor, accessHandler)
|
||||||
login.renderer = CreateRenderer(HandlerPrefix, staticStorage, config.LanguageCookieName)
|
login.renderer = CreateRenderer(HandlerPrefix, staticStorage, config.LanguageCookieName)
|
||||||
login.parser = form.NewParser()
|
login.parser = form.NewParser()
|
||||||
|
|
||||||
|
var err error
|
||||||
|
login.caches, err = startCaches(context.Background(), cacheConnectors)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
return login, nil
|
return login, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -201,3 +212,41 @@ func setUserContext(ctx context.Context, userID, resourceOwner string) context.C
|
|||||||
func (l *Login) baseURL(ctx context.Context) string {
|
func (l *Login) baseURL(ctx context.Context) string {
|
||||||
return http_utils.DomainContext(ctx).Origin() + HandlerPrefix
|
return http_utils.DomainContext(ctx).Origin() + HandlerPrefix
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Caches struct {
|
||||||
|
idpFormCallbacks cache.Cache[idpFormCallbackIndex, string, *idpFormCallback]
|
||||||
|
}
|
||||||
|
|
||||||
|
func startCaches(background context.Context, connectors connector.Connectors) (_ *Caches, err error) {
|
||||||
|
caches := new(Caches)
|
||||||
|
caches.idpFormCallbacks, err = connector.StartCache[idpFormCallbackIndex, string, *idpFormCallback](background, []idpFormCallbackIndex{idpFormCallbackIndexRequestID}, cache.PurposeIdPFormCallback, connectors.Config.IdPFormCallbacks, connectors)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return caches, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type idpFormCallbackIndex int
|
||||||
|
|
||||||
|
const (
|
||||||
|
idpFormCallbackIndexUnspecified idpFormCallbackIndex = iota
|
||||||
|
idpFormCallbackIndexRequestID
|
||||||
|
)
|
||||||
|
|
||||||
|
type idpFormCallback struct {
|
||||||
|
InstanceID string
|
||||||
|
State string
|
||||||
|
Form url.Values
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keys implements cache.Entry
|
||||||
|
func (c *idpFormCallback) Keys(i idpFormCallbackIndex) []string {
|
||||||
|
if i == idpFormCallbackIndexRequestID {
|
||||||
|
return []string{idpFormCallbackKey(c.InstanceID, c.State)}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func idpFormCallbackKey(instanceID, state string) string {
|
||||||
|
return instanceID + "-" + state
|
||||||
|
}
|
||||||
|
@ -108,7 +108,7 @@ InitMFAPrompt:
|
|||||||
InitMFAOTP:
|
InitMFAOTP:
|
||||||
Title: Zwei-Faktor-Authentifizierung
|
Title: Zwei-Faktor-Authentifizierung
|
||||||
Description: Erstelle deinen Zweitfaktor. Installiere eine Authentifizierungs-App, wenn du noch keine hast.
|
Description: Erstelle deinen Zweitfaktor. Installiere eine Authentifizierungs-App, wenn du noch keine hast.
|
||||||
OTPDescription: Scanne den Code mit einer Authentifizierungs-App (z.B. Google/Mircorsoft Authenticator, Authy) oder kopiere das Secret und gib anschliessend den Code ein.
|
OTPDescription: Scanne den Code mit einer Authentifizierungs-App (z.B. Google/Microsoft Authenticator, Authy) oder kopiere das Secret und gib anschliessend den Code ein.
|
||||||
SecretLabel: Secret
|
SecretLabel: Secret
|
||||||
CodeLabel: Code
|
CodeLabel: Code
|
||||||
NextButtonText: Weiter
|
NextButtonText: Weiter
|
||||||
|
1
internal/cache/cache.go
vendored
1
internal/cache/cache.go
vendored
@ -17,6 +17,7 @@ const (
|
|||||||
PurposeAuthzInstance
|
PurposeAuthzInstance
|
||||||
PurposeMilestones
|
PurposeMilestones
|
||||||
PurposeOrganization
|
PurposeOrganization
|
||||||
|
PurposeIdPFormCallback
|
||||||
)
|
)
|
||||||
|
|
||||||
// Cache stores objects with a value of type `V`.
|
// Cache stores objects with a value of type `V`.
|
||||||
|
7
internal/cache/connector/connector.go
vendored
7
internal/cache/connector/connector.go
vendored
@ -19,9 +19,10 @@ type CachesConfig struct {
|
|||||||
Postgres pg.Config
|
Postgres pg.Config
|
||||||
Redis redis.Config
|
Redis redis.Config
|
||||||
}
|
}
|
||||||
Instance *cache.Config
|
Instance *cache.Config
|
||||||
Milestones *cache.Config
|
Milestones *cache.Config
|
||||||
Organization *cache.Config
|
Organization *cache.Config
|
||||||
|
IdPFormCallbacks *cache.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
type Connectors struct {
|
type Connectors struct {
|
||||||
|
4
internal/cache/connector/redis/get.lua
vendored
4
internal/cache/connector/redis/get.lua
vendored
@ -13,8 +13,8 @@ end
|
|||||||
|
|
||||||
-- max-age must be checked manually
|
-- max-age must be checked manually
|
||||||
local expiry = getCall("HGET", object_id, "expiry")
|
local expiry = getCall("HGET", object_id, "expiry")
|
||||||
if not (expiry == nil) and expiry > 0 then
|
if not (expiry == nil) and tonumber(expiry) > 0 then
|
||||||
if getTime() > expiry then
|
if getTime() > tonumber(expiry) then
|
||||||
remove(object_id)
|
remove(object_id)
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
|
12
internal/cache/purpose_enumer.go
vendored
12
internal/cache/purpose_enumer.go
vendored
@ -7,11 +7,11 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
const _PurposeName = "unspecifiedauthz_instancemilestonesorganization"
|
const _PurposeName = "unspecifiedauthz_instancemilestonesorganizationid_p_form_callback"
|
||||||
|
|
||||||
var _PurposeIndex = [...]uint8{0, 11, 25, 35, 47}
|
var _PurposeIndex = [...]uint8{0, 11, 25, 35, 47, 65}
|
||||||
|
|
||||||
const _PurposeLowerName = "unspecifiedauthz_instancemilestonesorganization"
|
const _PurposeLowerName = "unspecifiedauthz_instancemilestonesorganizationid_p_form_callback"
|
||||||
|
|
||||||
func (i Purpose) String() string {
|
func (i Purpose) String() string {
|
||||||
if i < 0 || i >= Purpose(len(_PurposeIndex)-1) {
|
if i < 0 || i >= Purpose(len(_PurposeIndex)-1) {
|
||||||
@ -28,9 +28,10 @@ func _PurposeNoOp() {
|
|||||||
_ = x[PurposeAuthzInstance-(1)]
|
_ = x[PurposeAuthzInstance-(1)]
|
||||||
_ = x[PurposeMilestones-(2)]
|
_ = x[PurposeMilestones-(2)]
|
||||||
_ = x[PurposeOrganization-(3)]
|
_ = x[PurposeOrganization-(3)]
|
||||||
|
_ = x[PurposeIdPFormCallback-(4)]
|
||||||
}
|
}
|
||||||
|
|
||||||
var _PurposeValues = []Purpose{PurposeUnspecified, PurposeAuthzInstance, PurposeMilestones, PurposeOrganization}
|
var _PurposeValues = []Purpose{PurposeUnspecified, PurposeAuthzInstance, PurposeMilestones, PurposeOrganization, PurposeIdPFormCallback}
|
||||||
|
|
||||||
var _PurposeNameToValueMap = map[string]Purpose{
|
var _PurposeNameToValueMap = map[string]Purpose{
|
||||||
_PurposeName[0:11]: PurposeUnspecified,
|
_PurposeName[0:11]: PurposeUnspecified,
|
||||||
@ -41,6 +42,8 @@ var _PurposeNameToValueMap = map[string]Purpose{
|
|||||||
_PurposeLowerName[25:35]: PurposeMilestones,
|
_PurposeLowerName[25:35]: PurposeMilestones,
|
||||||
_PurposeName[35:47]: PurposeOrganization,
|
_PurposeName[35:47]: PurposeOrganization,
|
||||||
_PurposeLowerName[35:47]: PurposeOrganization,
|
_PurposeLowerName[35:47]: PurposeOrganization,
|
||||||
|
_PurposeName[47:65]: PurposeIdPFormCallback,
|
||||||
|
_PurposeLowerName[47:65]: PurposeIdPFormCallback,
|
||||||
}
|
}
|
||||||
|
|
||||||
var _PurposeNames = []string{
|
var _PurposeNames = []string{
|
||||||
@ -48,6 +51,7 @@ var _PurposeNames = []string{
|
|||||||
_PurposeName[11:25],
|
_PurposeName[11:25],
|
||||||
_PurposeName[25:35],
|
_PurposeName[25:35],
|
||||||
_PurposeName[35:47],
|
_PurposeName[35:47],
|
||||||
|
_PurposeName[47:65],
|
||||||
}
|
}
|
||||||
|
|
||||||
// PurposeString retrieves an enum value from the enum constants string name.
|
// PurposeString retrieves an enum value from the enum constants string name.
|
||||||
|
@ -57,6 +57,28 @@ func (c *Commands) ResendUserEmailReturnCode(ctx context.Context, userID string,
|
|||||||
return c.resendUserEmailCode(ctx, userID, alg, true, "")
|
return c.resendUserEmailCode(ctx, userID, alg, true, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SendUserEmailCode generates a new code
|
||||||
|
// and triggers a notification e-mail with the default confirmation URL format.
|
||||||
|
func (c *Commands) SendUserEmailCode(ctx context.Context, userID string, alg crypto.EncryptionAlgorithm) (*domain.Email, error) {
|
||||||
|
return c.sendUserEmailCode(ctx, userID, alg, false, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendUserEmailCodeURLTemplate generates a new code
|
||||||
|
// and triggers a notification e-mail with the confirmation URL rendered from the passed urlTmpl.
|
||||||
|
// urlTmpl must be a valid [tmpl.Template].
|
||||||
|
func (c *Commands) SendUserEmailCodeURLTemplate(ctx context.Context, userID string, alg crypto.EncryptionAlgorithm, urlTmpl string) (*domain.Email, error) {
|
||||||
|
if err := domain.RenderConfirmURLTemplate(io.Discard, urlTmpl, userID, "code", "orgID"); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return c.sendUserEmailCode(ctx, userID, alg, false, urlTmpl)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendUserEmailReturnCode generates a new code and does not send a notification email.
|
||||||
|
// The generated plain text code will be set in the returned Email object.
|
||||||
|
func (c *Commands) SendUserEmailReturnCode(ctx context.Context, userID string, alg crypto.EncryptionAlgorithm) (*domain.Email, error) {
|
||||||
|
return c.sendUserEmailCode(ctx, userID, alg, true, "")
|
||||||
|
}
|
||||||
|
|
||||||
// ChangeUserEmailVerified sets a user's email address and marks it is verified.
|
// ChangeUserEmailVerified sets a user's email address and marks it is verified.
|
||||||
// No code is generated and no confirmation e-mail is send.
|
// No code is generated and no confirmation e-mail is send.
|
||||||
func (c *Commands) ChangeUserEmailVerified(ctx context.Context, userID, email string) (*domain.Email, error) {
|
func (c *Commands) ChangeUserEmailVerified(ctx context.Context, userID, email string) (*domain.Email, error) {
|
||||||
@ -89,7 +111,16 @@ func (c *Commands) resendUserEmailCode(ctx context.Context, userID string, alg c
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
gen := crypto.NewEncryptionGenerator(*config, alg)
|
gen := crypto.NewEncryptionGenerator(*config, alg)
|
||||||
return c.resendUserEmailCodeWithGenerator(ctx, userID, gen, returnCode, urlTmpl)
|
return c.sendUserEmailCodeWithGenerator(ctx, userID, gen, returnCode, urlTmpl, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Commands) sendUserEmailCode(ctx context.Context, userID string, alg crypto.EncryptionAlgorithm, returnCode bool, urlTmpl string) (*domain.Email, error) {
|
||||||
|
config, err := cryptoGeneratorConfig(ctx, c.eventstore.Filter, domain.SecretGeneratorTypeVerifyEmailCode) //nolint:staticcheck
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
gen := crypto.NewEncryptionGenerator(*config, alg)
|
||||||
|
return c.sendUserEmailCodeWithGenerator(ctx, userID, gen, returnCode, urlTmpl, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// changeUserEmailWithGenerator set a user's email address.
|
// changeUserEmailWithGenerator set a user's email address.
|
||||||
@ -104,8 +135,8 @@ func (c *Commands) changeUserEmailWithGenerator(ctx context.Context, userID, ema
|
|||||||
return cmd.Push(ctx)
|
return cmd.Push(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Commands) resendUserEmailCodeWithGenerator(ctx context.Context, userID string, gen crypto.Generator, returnCode bool, urlTmpl string) (*domain.Email, error) {
|
func (c *Commands) sendUserEmailCodeWithGenerator(ctx context.Context, userID string, gen crypto.Generator, returnCode bool, urlTmpl string, existingCheck bool) (*domain.Email, error) {
|
||||||
cmd, err := c.resendUserEmailCodeWithGeneratorEvents(ctx, userID, gen, returnCode, urlTmpl)
|
cmd, err := c.sendUserEmailCodeWithGeneratorEvents(ctx, userID, gen, returnCode, urlTmpl, existingCheck)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -129,7 +160,7 @@ func (c *Commands) changeUserEmailWithGeneratorEvents(ctx context.Context, userI
|
|||||||
return cmd, nil
|
return cmd, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Commands) resendUserEmailCodeWithGeneratorEvents(ctx context.Context, userID string, gen crypto.Generator, returnCode bool, urlTmpl string) (*UserEmailEvents, error) {
|
func (c *Commands) sendUserEmailCodeWithGeneratorEvents(ctx context.Context, userID string, gen crypto.Generator, returnCode bool, urlTmpl string, existingCheck bool) (*UserEmailEvents, error) {
|
||||||
cmd, err := c.NewUserEmailEvents(ctx, userID)
|
cmd, err := c.NewUserEmailEvents(ctx, userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -137,7 +168,7 @@ func (c *Commands) resendUserEmailCodeWithGeneratorEvents(ctx context.Context, u
|
|||||||
if err = c.checkPermissionUpdateUser(ctx, cmd.aggregate.ResourceOwner, userID); err != nil {
|
if err = c.checkPermissionUpdateUser(ctx, cmd.aggregate.ResourceOwner, userID); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if cmd.model.Code == nil {
|
if existingCheck && cmd.model.Code == nil {
|
||||||
return nil, zerrors.ThrowPreconditionFailed(err, "EMAIL-5w5ilin4yt", "Errors.User.Code.Empty")
|
return nil, zerrors.ThrowPreconditionFailed(err, "EMAIL-5w5ilin4yt", "Errors.User.Code.Empty")
|
||||||
}
|
}
|
||||||
if err = cmd.AddGeneratedCode(ctx, gen, urlTmpl, returnCode); err != nil {
|
if err = cmd.AddGeneratedCode(ctx, gen, urlTmpl, returnCode); err != nil {
|
||||||
|
@ -512,6 +512,85 @@ func TestCommands_ResendUserEmailCode(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCommands_SendUserEmailCode(t *testing.T) {
|
||||||
|
type fields struct {
|
||||||
|
eventstore *eventstore.Eventstore
|
||||||
|
checkPermission domain.PermissionCheck
|
||||||
|
}
|
||||||
|
type args struct {
|
||||||
|
userID string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
args args
|
||||||
|
wantErr error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "missing permission",
|
||||||
|
fields: fields{
|
||||||
|
eventstore: eventstoreExpect(
|
||||||
|
t,
|
||||||
|
expectFilter(
|
||||||
|
eventFromEventPusher(
|
||||||
|
instance.NewSecretGeneratorAddedEvent(context.Background(),
|
||||||
|
&instance.NewAggregate("inst1").Aggregate,
|
||||||
|
domain.SecretGeneratorTypeVerifyEmailCode,
|
||||||
|
12, time.Minute, true, true, true, true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
expectFilter(
|
||||||
|
eventFromEventPusher(
|
||||||
|
user.NewHumanAddedEvent(context.Background(),
|
||||||
|
&user.NewAggregate("user1", "org1").Aggregate,
|
||||||
|
"username",
|
||||||
|
"firstname",
|
||||||
|
"lastname",
|
||||||
|
"nickname",
|
||||||
|
"displayname",
|
||||||
|
language.German,
|
||||||
|
domain.GenderUnspecified,
|
||||||
|
"email@test.ch",
|
||||||
|
true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
eventFromEventPusher(
|
||||||
|
user.NewHumanEmailCodeAddedEventV2(context.Background(),
|
||||||
|
&user.NewAggregate("user1", "org1").Aggregate,
|
||||||
|
&crypto.CryptoValue{
|
||||||
|
CryptoType: crypto.TypeEncryption,
|
||||||
|
Algorithm: "enc",
|
||||||
|
KeyID: "id",
|
||||||
|
Crypted: []byte("a"),
|
||||||
|
},
|
||||||
|
time.Hour*1,
|
||||||
|
"", false, "",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
checkPermission: newMockPermissionCheckNotAllowed(),
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
userID: "user1",
|
||||||
|
},
|
||||||
|
wantErr: zerrors.ThrowPermissionDenied(nil, "AUTHZ-HKJD33", "Errors.PermissionDenied"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
c := &Commands{
|
||||||
|
eventstore: tt.fields.eventstore,
|
||||||
|
checkPermission: tt.fields.checkPermission,
|
||||||
|
}
|
||||||
|
_, err := c.SendUserEmailCode(context.Background(), tt.args.userID, crypto.CreateMockEncryptionAlg(gomock.NewController(t)))
|
||||||
|
require.ErrorIs(t, err, tt.wantErr)
|
||||||
|
// successful cases are tested in TestCommands_sendUserEmailCodeWithGeneratorEvents
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestCommands_ResendUserEmailCodeURLTemplate(t *testing.T) {
|
func TestCommands_ResendUserEmailCodeURLTemplate(t *testing.T) {
|
||||||
type fields struct {
|
type fields struct {
|
||||||
eventstore *eventstore.Eventstore
|
eventstore *eventstore.Eventstore
|
||||||
@ -638,7 +717,99 @@ func TestCommands_ResendUserEmailCodeURLTemplate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
_, err := c.ResendUserEmailCodeURLTemplate(context.Background(), tt.args.userID, crypto.CreateMockEncryptionAlg(gomock.NewController(t)), tt.args.urlTmpl)
|
_, err := c.ResendUserEmailCodeURLTemplate(context.Background(), tt.args.userID, crypto.CreateMockEncryptionAlg(gomock.NewController(t)), tt.args.urlTmpl)
|
||||||
require.ErrorIs(t, err, tt.wantErr)
|
require.ErrorIs(t, err, tt.wantErr)
|
||||||
// successful cases are tested in TestCommands_resendUserEmailCodeWithGenerator
|
// successful cases are tested in TestCommands_sendUserEmailCodeWithGeneratorEvents
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCommands_SendUserEmailCodeURLTemplate(t *testing.T) {
|
||||||
|
type fields struct {
|
||||||
|
eventstore *eventstore.Eventstore
|
||||||
|
checkPermission domain.PermissionCheck
|
||||||
|
}
|
||||||
|
type args struct {
|
||||||
|
userID string
|
||||||
|
urlTmpl string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
args args
|
||||||
|
wantErr error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "invalid template",
|
||||||
|
fields: fields{
|
||||||
|
eventstore: eventstoreExpect(t),
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
userID: "user1",
|
||||||
|
urlTmpl: "{{",
|
||||||
|
},
|
||||||
|
wantErr: zerrors.ThrowInvalidArgument(nil, "DOMAIN-oGh5e", "Errors.User.InvalidURLTemplate"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "permission missing",
|
||||||
|
fields: fields{
|
||||||
|
eventstore: eventstoreExpect(
|
||||||
|
t,
|
||||||
|
expectFilter(
|
||||||
|
eventFromEventPusher(
|
||||||
|
instance.NewSecretGeneratorAddedEvent(context.Background(),
|
||||||
|
&instance.NewAggregate("inst1").Aggregate,
|
||||||
|
domain.SecretGeneratorTypeVerifyEmailCode,
|
||||||
|
12, time.Minute, true, true, true, true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
expectFilter(
|
||||||
|
eventFromEventPusher(
|
||||||
|
user.NewHumanAddedEvent(context.Background(),
|
||||||
|
&user.NewAggregate("user1", "org1").Aggregate,
|
||||||
|
"username",
|
||||||
|
"firstname",
|
||||||
|
"lastname",
|
||||||
|
"nickname",
|
||||||
|
"displayname",
|
||||||
|
language.German,
|
||||||
|
domain.GenderUnspecified,
|
||||||
|
"email@test.ch",
|
||||||
|
true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
eventFromEventPusher(
|
||||||
|
user.NewHumanEmailCodeAddedEventV2(context.Background(),
|
||||||
|
&user.NewAggregate("user1", "org1").Aggregate,
|
||||||
|
&crypto.CryptoValue{
|
||||||
|
CryptoType: crypto.TypeEncryption,
|
||||||
|
Algorithm: "enc",
|
||||||
|
KeyID: "id",
|
||||||
|
Crypted: []byte("a"),
|
||||||
|
},
|
||||||
|
time.Hour*1,
|
||||||
|
"", false, "",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
checkPermission: newMockPermissionCheckNotAllowed(),
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
userID: "user1",
|
||||||
|
urlTmpl: "https://example.com/email/verify?userID={{.UserID}}&code={{.Code}}&orgID={{.OrgID}}",
|
||||||
|
},
|
||||||
|
wantErr: zerrors.ThrowPermissionDenied(nil, "AUTHZ-HKJD33", "Errors.PermissionDenied"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
c := &Commands{
|
||||||
|
eventstore: tt.fields.eventstore,
|
||||||
|
checkPermission: tt.fields.checkPermission,
|
||||||
|
}
|
||||||
|
_, err := c.SendUserEmailCodeURLTemplate(context.Background(), tt.args.userID, crypto.CreateMockEncryptionAlg(gomock.NewController(t)), tt.args.urlTmpl)
|
||||||
|
require.ErrorIs(t, err, tt.wantErr)
|
||||||
|
// successful cases are tested in TestCommands_sendUserEmailCodeWithGeneratorEvents
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -760,6 +931,85 @@ func TestCommands_ResendUserEmailReturnCode(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCommands_SendUserEmailReturnCode(t *testing.T) {
|
||||||
|
type fields struct {
|
||||||
|
eventstore *eventstore.Eventstore
|
||||||
|
checkPermission domain.PermissionCheck
|
||||||
|
}
|
||||||
|
type args struct {
|
||||||
|
userID string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
args args
|
||||||
|
wantErr error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "missing permission",
|
||||||
|
fields: fields{
|
||||||
|
eventstore: eventstoreExpect(
|
||||||
|
t,
|
||||||
|
expectFilter(
|
||||||
|
eventFromEventPusher(
|
||||||
|
instance.NewSecretGeneratorAddedEvent(context.Background(),
|
||||||
|
&instance.NewAggregate("inst1").Aggregate,
|
||||||
|
domain.SecretGeneratorTypeVerifyEmailCode,
|
||||||
|
12, time.Minute, true, true, true, true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
expectFilter(
|
||||||
|
eventFromEventPusher(
|
||||||
|
user.NewHumanAddedEvent(context.Background(),
|
||||||
|
&user.NewAggregate("user1", "org1").Aggregate,
|
||||||
|
"username",
|
||||||
|
"firstname",
|
||||||
|
"lastname",
|
||||||
|
"nickname",
|
||||||
|
"displayname",
|
||||||
|
language.German,
|
||||||
|
domain.GenderUnspecified,
|
||||||
|
"email@test.ch",
|
||||||
|
true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
eventFromEventPusher(
|
||||||
|
user.NewHumanEmailCodeAddedEventV2(context.Background(),
|
||||||
|
&user.NewAggregate("user1", "org1").Aggregate,
|
||||||
|
&crypto.CryptoValue{
|
||||||
|
CryptoType: crypto.TypeEncryption,
|
||||||
|
Algorithm: "enc",
|
||||||
|
KeyID: "id",
|
||||||
|
Crypted: []byte("a"),
|
||||||
|
},
|
||||||
|
time.Hour*1,
|
||||||
|
"", false, "",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
checkPermission: newMockPermissionCheckNotAllowed(),
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
userID: "user1",
|
||||||
|
},
|
||||||
|
wantErr: zerrors.ThrowPermissionDenied(nil, "AUTHZ-HKJD33", "Errors.PermissionDenied"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
c := &Commands{
|
||||||
|
eventstore: tt.fields.eventstore,
|
||||||
|
checkPermission: tt.fields.checkPermission,
|
||||||
|
}
|
||||||
|
_, err := c.SendUserEmailReturnCode(context.Background(), tt.args.userID, crypto.CreateMockEncryptionAlg(gomock.NewController(t)))
|
||||||
|
require.ErrorIs(t, err, tt.wantErr)
|
||||||
|
// successful cases are tested in TestCommands_sendUserEmailCodeWithGeneratorEvents
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestCommands_ChangeUserEmailVerified(t *testing.T) {
|
func TestCommands_ChangeUserEmailVerified(t *testing.T) {
|
||||||
type fields struct {
|
type fields struct {
|
||||||
eventstore *eventstore.Eventstore
|
eventstore *eventstore.Eventstore
|
||||||
@ -1218,15 +1468,16 @@ func TestCommands_changeUserEmailWithGenerator(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCommands_resendUserEmailCodeWithGeneratorEvents(t *testing.T) {
|
func TestCommands_sendUserEmailCodeWithGeneratorEvents(t *testing.T) {
|
||||||
type fields struct {
|
type fields struct {
|
||||||
eventstore *eventstore.Eventstore
|
eventstore *eventstore.Eventstore
|
||||||
checkPermission domain.PermissionCheck
|
checkPermission domain.PermissionCheck
|
||||||
}
|
}
|
||||||
type args struct {
|
type args struct {
|
||||||
userID string
|
userID string
|
||||||
returnCode bool
|
returnCode bool
|
||||||
urlTmpl string
|
urlTmpl string
|
||||||
|
checkExisting bool
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@ -1247,37 +1498,6 @@ func TestCommands_resendUserEmailCodeWithGeneratorEvents(t *testing.T) {
|
|||||||
},
|
},
|
||||||
wantErr: zerrors.ThrowInvalidArgument(nil, "COMMAND-0Gzs3", "Errors.User.Email.IDMissing"),
|
wantErr: zerrors.ThrowInvalidArgument(nil, "COMMAND-0Gzs3", "Errors.User.Email.IDMissing"),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "resend code, missing code",
|
|
||||||
fields: fields{
|
|
||||||
eventstore: eventstoreExpect(
|
|
||||||
t,
|
|
||||||
expectFilter(
|
|
||||||
eventFromEventPusher(
|
|
||||||
user.NewHumanAddedEvent(context.Background(),
|
|
||||||
&user.NewAggregate("user1", "org1").Aggregate,
|
|
||||||
"username",
|
|
||||||
"firstname",
|
|
||||||
"lastname",
|
|
||||||
"nickname",
|
|
||||||
"displayname",
|
|
||||||
language.German,
|
|
||||||
domain.GenderUnspecified,
|
|
||||||
"email@test.ch",
|
|
||||||
true,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
checkPermission: newMockPermissionCheckAllowed(),
|
|
||||||
},
|
|
||||||
args: args{
|
|
||||||
userID: "user1",
|
|
||||||
returnCode: false,
|
|
||||||
urlTmpl: "",
|
|
||||||
},
|
|
||||||
wantErr: zerrors.ThrowPreconditionFailed(nil, "EMAIL-5w5ilin4yt", "Errors.User.Code.Empty"),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: "missing permission",
|
name: "missing permission",
|
||||||
fields: fields{
|
fields: fields{
|
||||||
@ -1322,6 +1542,58 @@ func TestCommands_resendUserEmailCodeWithGeneratorEvents(t *testing.T) {
|
|||||||
},
|
},
|
||||||
wantErr: zerrors.ThrowPermissionDenied(nil, "AUTHZ-HKJD33", "Errors.PermissionDenied"),
|
wantErr: zerrors.ThrowPermissionDenied(nil, "AUTHZ-HKJD33", "Errors.PermissionDenied"),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "send code",
|
||||||
|
fields: fields{
|
||||||
|
eventstore: eventstoreExpect(
|
||||||
|
t,
|
||||||
|
expectFilter(
|
||||||
|
eventFromEventPusher(
|
||||||
|
user.NewHumanAddedEvent(context.Background(),
|
||||||
|
&user.NewAggregate("user1", "org1").Aggregate,
|
||||||
|
"username",
|
||||||
|
"firstname",
|
||||||
|
"lastname",
|
||||||
|
"nickname",
|
||||||
|
"displayname",
|
||||||
|
language.German,
|
||||||
|
domain.GenderUnspecified,
|
||||||
|
"email@test.ch",
|
||||||
|
true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
expectPush(
|
||||||
|
user.NewHumanEmailCodeAddedEventV2(context.Background(),
|
||||||
|
&user.NewAggregate("user1", "org1").Aggregate,
|
||||||
|
&crypto.CryptoValue{
|
||||||
|
CryptoType: crypto.TypeEncryption,
|
||||||
|
Algorithm: "enc",
|
||||||
|
KeyID: "id",
|
||||||
|
Crypted: []byte("a"),
|
||||||
|
},
|
||||||
|
time.Hour*1,
|
||||||
|
"", false, "",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
checkPermission: newMockPermissionCheckAllowed(),
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
userID: "user1",
|
||||||
|
returnCode: false,
|
||||||
|
urlTmpl: "",
|
||||||
|
checkExisting: false,
|
||||||
|
},
|
||||||
|
want: &domain.Email{
|
||||||
|
ObjectRoot: models.ObjectRoot{
|
||||||
|
AggregateID: "user1",
|
||||||
|
ResourceOwner: "org1",
|
||||||
|
},
|
||||||
|
EmailAddress: "email@test.ch",
|
||||||
|
IsEmailVerified: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "resend code",
|
name: "resend code",
|
||||||
fields: fields{
|
fields: fields{
|
||||||
@ -1373,9 +1645,10 @@ func TestCommands_resendUserEmailCodeWithGeneratorEvents(t *testing.T) {
|
|||||||
checkPermission: newMockPermissionCheckAllowed(),
|
checkPermission: newMockPermissionCheckAllowed(),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
userID: "user1",
|
userID: "user1",
|
||||||
returnCode: false,
|
returnCode: false,
|
||||||
urlTmpl: "",
|
urlTmpl: "",
|
||||||
|
checkExisting: true,
|
||||||
},
|
},
|
||||||
want: &domain.Email{
|
want: &domain.Email{
|
||||||
ObjectRoot: models.ObjectRoot{
|
ObjectRoot: models.ObjectRoot{
|
||||||
@ -1387,7 +1660,7 @@ func TestCommands_resendUserEmailCodeWithGeneratorEvents(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "resend code, return code",
|
name: "resend code, missing code",
|
||||||
fields: fields{
|
fields: fields{
|
||||||
eventstore: eventstoreExpect(
|
eventstore: eventstoreExpect(
|
||||||
t,
|
t,
|
||||||
@ -1406,17 +1679,36 @@ func TestCommands_resendUserEmailCodeWithGeneratorEvents(t *testing.T) {
|
|||||||
true,
|
true,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
checkPermission: newMockPermissionCheckAllowed(),
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
userID: "user1",
|
||||||
|
returnCode: false,
|
||||||
|
urlTmpl: "",
|
||||||
|
checkExisting: true,
|
||||||
|
},
|
||||||
|
wantErr: zerrors.ThrowPreconditionFailed(nil, "EMAIL-5w5ilin4yt", "Errors.User.Code.Empty"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "send code, return code",
|
||||||
|
fields: fields{
|
||||||
|
eventstore: eventstoreExpect(
|
||||||
|
t,
|
||||||
|
expectFilter(
|
||||||
eventFromEventPusher(
|
eventFromEventPusher(
|
||||||
user.NewHumanEmailCodeAddedEventV2(context.Background(),
|
user.NewHumanAddedEvent(context.Background(),
|
||||||
&user.NewAggregate("user1", "org1").Aggregate,
|
&user.NewAggregate("user1", "org1").Aggregate,
|
||||||
&crypto.CryptoValue{
|
"username",
|
||||||
CryptoType: crypto.TypeEncryption,
|
"firstname",
|
||||||
Algorithm: "enc",
|
"lastname",
|
||||||
KeyID: "id",
|
"nickname",
|
||||||
Crypted: []byte("a"),
|
"displayname",
|
||||||
},
|
language.German,
|
||||||
time.Hour*1,
|
domain.GenderUnspecified,
|
||||||
"", false, "",
|
"email@test.ch",
|
||||||
|
true,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -1437,9 +1729,10 @@ func TestCommands_resendUserEmailCodeWithGeneratorEvents(t *testing.T) {
|
|||||||
checkPermission: newMockPermissionCheckAllowed(),
|
checkPermission: newMockPermissionCheckAllowed(),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
userID: "user1",
|
userID: "user1",
|
||||||
returnCode: true,
|
returnCode: true,
|
||||||
urlTmpl: "",
|
urlTmpl: "",
|
||||||
|
checkExisting: false,
|
||||||
},
|
},
|
||||||
want: &domain.Email{
|
want: &domain.Email{
|
||||||
ObjectRoot: models.ObjectRoot{
|
ObjectRoot: models.ObjectRoot{
|
||||||
@ -1452,7 +1745,7 @@ func TestCommands_resendUserEmailCodeWithGeneratorEvents(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "resend code, URL template",
|
name: "send code, URL template",
|
||||||
fields: fields{
|
fields: fields{
|
||||||
eventstore: eventstoreExpect(
|
eventstore: eventstoreExpect(
|
||||||
t,
|
t,
|
||||||
@ -1471,19 +1764,6 @@ func TestCommands_resendUserEmailCodeWithGeneratorEvents(t *testing.T) {
|
|||||||
true,
|
true,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
eventFromEventPusher(
|
|
||||||
user.NewHumanEmailCodeAddedEventV2(context.Background(),
|
|
||||||
&user.NewAggregate("user1", "org1").Aggregate,
|
|
||||||
&crypto.CryptoValue{
|
|
||||||
CryptoType: crypto.TypeEncryption,
|
|
||||||
Algorithm: "enc",
|
|
||||||
KeyID: "id",
|
|
||||||
Crypted: []byte("a"),
|
|
||||||
},
|
|
||||||
time.Hour*1,
|
|
||||||
"", false, "",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
expectPush(
|
expectPush(
|
||||||
user.NewHumanEmailCodeAddedEventV2(context.Background(),
|
user.NewHumanEmailCodeAddedEventV2(context.Background(),
|
||||||
@ -1502,9 +1782,10 @@ func TestCommands_resendUserEmailCodeWithGeneratorEvents(t *testing.T) {
|
|||||||
checkPermission: newMockPermissionCheckAllowed(),
|
checkPermission: newMockPermissionCheckAllowed(),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
userID: "user1",
|
userID: "user1",
|
||||||
returnCode: false,
|
returnCode: false,
|
||||||
urlTmpl: "https://example.com/email/verify?userID={{.UserID}}&code={{.Code}}&orgID={{.OrgID}}",
|
urlTmpl: "https://example.com/email/verify?userID={{.UserID}}&code={{.Code}}&orgID={{.OrgID}}",
|
||||||
|
checkExisting: false,
|
||||||
},
|
},
|
||||||
want: &domain.Email{
|
want: &domain.Email{
|
||||||
ObjectRoot: models.ObjectRoot{
|
ObjectRoot: models.ObjectRoot{
|
||||||
@ -1522,7 +1803,7 @@ func TestCommands_resendUserEmailCodeWithGeneratorEvents(t *testing.T) {
|
|||||||
eventstore: tt.fields.eventstore,
|
eventstore: tt.fields.eventstore,
|
||||||
checkPermission: tt.fields.checkPermission,
|
checkPermission: tt.fields.checkPermission,
|
||||||
}
|
}
|
||||||
got, err := c.resendUserEmailCodeWithGenerator(context.Background(), tt.args.userID, GetMockSecretGenerator(t), tt.args.returnCode, tt.args.urlTmpl)
|
got, err := c.sendUserEmailCodeWithGenerator(context.Background(), tt.args.userID, GetMockSecretGenerator(t), tt.args.returnCode, tt.args.urlTmpl, tt.args.checkExisting)
|
||||||
require.ErrorIs(t, err, tt.wantErr)
|
require.ErrorIs(t, err, tt.wantErr)
|
||||||
assert.Equal(t, tt.want, got)
|
assert.Equal(t, tt.want, got)
|
||||||
})
|
})
|
||||||
|
@ -271,7 +271,7 @@ func (i *Instance) CreateOrganizationWithUserID(ctx context.Context, name, userI
|
|||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Instance) CreateHumanUserVerified(ctx context.Context, org, email string) *user_v2.AddHumanUserResponse {
|
func (i *Instance) CreateHumanUserVerified(ctx context.Context, org, email, phone string) *user_v2.AddHumanUserResponse {
|
||||||
resp, err := i.Client.UserV2.AddHumanUser(ctx, &user_v2.AddHumanUserRequest{
|
resp, err := i.Client.UserV2.AddHumanUser(ctx, &user_v2.AddHumanUserRequest{
|
||||||
Organization: &object.Organization{
|
Organization: &object.Organization{
|
||||||
Org: &object.Organization_OrgId{
|
Org: &object.Organization_OrgId{
|
||||||
@ -292,7 +292,7 @@ func (i *Instance) CreateHumanUserVerified(ctx context.Context, org, email strin
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
Phone: &user_v2.SetHumanPhone{
|
Phone: &user_v2.SetHumanPhone{
|
||||||
Phone: "+41791234567",
|
Phone: phone,
|
||||||
Verification: &user_v2.SetHumanPhone_IsVerified{
|
Verification: &user_v2.SetHumanPhone_IsVerified{
|
||||||
IsVerified: true,
|
IsVerified: true,
|
||||||
},
|
},
|
||||||
@ -327,7 +327,7 @@ func (i *Instance) CreateUserIDPlink(ctx context.Context, userID, externalID, id
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Instance) RegisterUserPasskey(ctx context.Context, userID string) {
|
func (i *Instance) RegisterUserPasskey(ctx context.Context, userID string) string {
|
||||||
reg, err := i.Client.UserV2.CreatePasskeyRegistrationLink(ctx, &user_v2.CreatePasskeyRegistrationLinkRequest{
|
reg, err := i.Client.UserV2.CreatePasskeyRegistrationLink(ctx, &user_v2.CreatePasskeyRegistrationLinkRequest{
|
||||||
UserId: userID,
|
UserId: userID,
|
||||||
Medium: &user_v2.CreatePasskeyRegistrationLinkRequest_ReturnCode{},
|
Medium: &user_v2.CreatePasskeyRegistrationLinkRequest_ReturnCode{},
|
||||||
@ -350,9 +350,10 @@ func (i *Instance) RegisterUserPasskey(ctx context.Context, userID string) {
|
|||||||
PasskeyName: "nice name",
|
PasskeyName: "nice name",
|
||||||
})
|
})
|
||||||
logging.OnError(err).Panic("create user passkey")
|
logging.OnError(err).Panic("create user passkey")
|
||||||
|
return pkr.GetPasskeyId()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Instance) RegisterUserU2F(ctx context.Context, userID string) {
|
func (i *Instance) RegisterUserU2F(ctx context.Context, userID string) string {
|
||||||
pkr, err := i.Client.UserV2.RegisterU2F(ctx, &user_v2.RegisterU2FRequest{
|
pkr, err := i.Client.UserV2.RegisterU2F(ctx, &user_v2.RegisterU2FRequest{
|
||||||
UserId: userID,
|
UserId: userID,
|
||||||
Domain: i.Domain,
|
Domain: i.Domain,
|
||||||
@ -368,6 +369,21 @@ func (i *Instance) RegisterUserU2F(ctx context.Context, userID string) {
|
|||||||
TokenName: "nice name",
|
TokenName: "nice name",
|
||||||
})
|
})
|
||||||
logging.OnError(err).Panic("create user u2f")
|
logging.OnError(err).Panic("create user u2f")
|
||||||
|
return pkr.GetU2FId()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Instance) RegisterUserOTPSMS(ctx context.Context, userID string) {
|
||||||
|
_, err := i.Client.UserV2.AddOTPSMS(ctx, &user_v2.AddOTPSMSRequest{
|
||||||
|
UserId: userID,
|
||||||
|
})
|
||||||
|
logging.OnError(err).Panic("create user sms")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Instance) RegisterUserOTPEmail(ctx context.Context, userID string) {
|
||||||
|
_, err := i.Client.UserV2.AddOTPEmail(ctx, &user_v2.AddOTPEmailRequest{
|
||||||
|
UserId: userID,
|
||||||
|
})
|
||||||
|
logging.OnError(err).Panic("create user email")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Instance) SetUserPassword(ctx context.Context, userID, password string, changeRequired bool) *object.Details {
|
func (i *Instance) SetUserPassword(ctx context.Context, userID, password string, changeRequired bool) *object.Details {
|
||||||
|
@ -270,6 +270,14 @@ func NewUserAuthMethodTypesSearchQuery(values ...domain.UserAuthMethodType) (Sea
|
|||||||
return NewListQuery(UserAuthMethodColumnMethodType, list, ListIn)
|
return NewListQuery(UserAuthMethodColumnMethodType, list, ListIn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewUserAuthMethodStatesSearchQuery(values ...domain.MFAState) (SearchQuery, error) {
|
||||||
|
list := make([]interface{}, len(values))
|
||||||
|
for i, value := range values {
|
||||||
|
list[i] = value
|
||||||
|
}
|
||||||
|
return NewListQuery(UserAuthMethodColumnState, list, ListIn)
|
||||||
|
}
|
||||||
|
|
||||||
func (r *UserAuthMethodSearchQueries) AppendResourceOwnerQuery(orgID string) error {
|
func (r *UserAuthMethodSearchQueries) AppendResourceOwnerQuery(orgID string) error {
|
||||||
query, err := NewUserAuthMethodResourceOwnerSearchQuery(orgID)
|
query, err := NewUserAuthMethodResourceOwnerSearchQuery(orgID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -306,6 +314,15 @@ func (r *UserAuthMethodSearchQueries) AppendStateQuery(state domain.MFAState) er
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *UserAuthMethodSearchQueries) AppendStatesQuery(state ...domain.MFAState) error {
|
||||||
|
query, err := NewUserAuthMethodStatesSearchQuery(state...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
r.Queries = append(r.Queries, query)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (r *UserAuthMethodSearchQueries) AppendAuthMethodQuery(authMethod domain.UserAuthMethodType) error {
|
func (r *UserAuthMethodSearchQueries) AppendAuthMethodQuery(authMethod domain.UserAuthMethodType) error {
|
||||||
query, err := NewUserAuthMethodTypeSearchQuery(authMethod)
|
query, err := NewUserAuthMethodTypeSearchQuery(authMethod)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -30,6 +30,7 @@ message SearchQuery {
|
|||||||
NotQuery not_query = 13;
|
NotQuery not_query = 13;
|
||||||
InUserEmailsQuery in_user_emails_query = 14;
|
InUserEmailsQuery in_user_emails_query = 14;
|
||||||
OrganizationIdQuery organization_id_query = 15;
|
OrganizationIdQuery organization_id_query = 15;
|
||||||
|
PhoneQuery phone_query = 16;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -184,6 +185,26 @@ message EmailQuery {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Query for users with a specific phone.
|
||||||
|
message PhoneQuery {
|
||||||
|
string number = 1 [
|
||||||
|
(validate.rules).string = {max_len: 20},
|
||||||
|
(google.api.field_behavior) = REQUIRED,
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "Phone number of the user"
|
||||||
|
min_length: 1;
|
||||||
|
max_length: 20;
|
||||||
|
example: "\"+41791234567\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
zitadel.object.v2.TextQueryMethod method = 2 [
|
||||||
|
(validate.rules).enum.defined_only = true,
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "defines which text equality method is used";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
// Query for users with a specific state.
|
// Query for users with a specific state.
|
||||||
message LoginNameQuery {
|
message LoginNameQuery {
|
||||||
string login_name = 1 [
|
string login_name = 1 [
|
||||||
|
@ -276,6 +276,36 @@ message Passkey {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message AuthFactor {
|
||||||
|
AuthFactorState state = 1 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "current state of the auth factor";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
oneof type {
|
||||||
|
AuthFactorOTP otp = 2 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "TOTP second factor"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
AuthFactorU2F u2f = 3 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "U2F second factor"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
AuthFactorOTPSMS otp_sms = 4 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "SMS second factor"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
AuthFactorOTPEmail otp_email = 5 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "Email second factor"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
enum AuthFactorState {
|
enum AuthFactorState {
|
||||||
AUTH_FACTOR_STATE_UNSPECIFIED = 0;
|
AUTH_FACTOR_STATE_UNSPECIFIED = 0;
|
||||||
AUTH_FACTOR_STATE_NOT_READY = 1;
|
AUTH_FACTOR_STATE_NOT_READY = 1;
|
||||||
@ -283,6 +313,23 @@ enum AuthFactorState {
|
|||||||
AUTH_FACTOR_STATE_REMOVED = 3;
|
AUTH_FACTOR_STATE_REMOVED = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message AuthFactorOTP {}
|
||||||
|
message AuthFactorOTPSMS {}
|
||||||
|
message AuthFactorOTPEmail {}
|
||||||
|
|
||||||
|
message AuthFactorU2F {
|
||||||
|
string id = 1 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "\"69629023906488334\""
|
||||||
|
}
|
||||||
|
];
|
||||||
|
string name = 2 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "\"fido key\""
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
message SendInviteCode {
|
message SendInviteCode {
|
||||||
// Optionally set a url_template, which will be used in the invite mail sent by ZITADEL to guide the user to your invitation page.
|
// Optionally set a url_template, which will be used in the invite mail sent by ZITADEL to guide the user to your invitation page.
|
||||||
// If no template is set, the default ZITADEL url will be used.
|
// If no template is set, the default ZITADEL url will be used.
|
||||||
|
@ -252,9 +252,34 @@ service UserService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Send code to verify user email
|
||||||
|
//
|
||||||
|
// Send code to verify user email.
|
||||||
|
rpc SendEmailCode (SendEmailCodeRequest) returns (SendEmailCodeResponse) {
|
||||||
|
option (google.api.http) = {
|
||||||
|
post: "/v2/users/{user_id}/email/send"
|
||||||
|
body: "*"
|
||||||
|
};
|
||||||
|
|
||||||
|
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
||||||
|
auth_option: {
|
||||||
|
permission: "authenticated"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||||
|
responses: {
|
||||||
|
key: "200"
|
||||||
|
value: {
|
||||||
|
description: "OK";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Verify the email
|
// Verify the email
|
||||||
//
|
//
|
||||||
// Verify the email with the generated code..
|
// Verify the email with the generated code.
|
||||||
rpc VerifyEmail (VerifyEmailRequest) returns (VerifyEmailResponse) {
|
rpc VerifyEmail (VerifyEmailRequest) returns (VerifyEmailResponse) {
|
||||||
option (google.api.http) = {
|
option (google.api.http) = {
|
||||||
post: "/v2/users/{user_id}/email/verify"
|
post: "/v2/users/{user_id}/email/verify"
|
||||||
@ -1085,6 +1110,28 @@ service UserService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rpc ListAuthenticationFactors(ListAuthenticationFactorsRequest) returns (ListAuthenticationFactorsResponse) {
|
||||||
|
option (google.api.http) = {
|
||||||
|
post: "/v2/users/{user_id}/authentication_factors/_search"
|
||||||
|
};
|
||||||
|
|
||||||
|
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
||||||
|
auth_option: {
|
||||||
|
permission: "authenticated"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||||
|
responses: {
|
||||||
|
key: "200"
|
||||||
|
value: {
|
||||||
|
description: "OK";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// Create an invite code for a user
|
// Create an invite code for a user
|
||||||
//
|
//
|
||||||
// Create an invite code for a user to initialize their first authentication method (password, passkeys, IdP) depending on the organization's available methods.
|
// Create an invite code for a user to initialize their first authentication method (password, passkeys, IdP) depending on the organization's available methods.
|
||||||
@ -1310,6 +1357,29 @@ message ResendEmailCodeResponse{
|
|||||||
optional string verification_code = 2;
|
optional string verification_code = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message SendEmailCodeRequest{
|
||||||
|
string user_id = 1 [
|
||||||
|
(validate.rules).string = {min_len: 1, max_len: 200},
|
||||||
|
(google.api.field_behavior) = REQUIRED,
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
min_length: 1;
|
||||||
|
max_length: 200;
|
||||||
|
example: "\"69629026806489455\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
// if no verification is specified, an email is sent with the default url
|
||||||
|
oneof verification {
|
||||||
|
SendEmailVerificationCode send_code = 2;
|
||||||
|
ReturnEmailVerificationCode return_code = 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message SendEmailCodeResponse{
|
||||||
|
zitadel.object.v2.Details details = 1;
|
||||||
|
// in case the verification was set to return_code, the code will be returned
|
||||||
|
optional string verification_code = 2;
|
||||||
|
}
|
||||||
|
|
||||||
message VerifyEmailRequest{
|
message VerifyEmailRequest{
|
||||||
string user_id = 1 [
|
string user_id = 1 [
|
||||||
(validate.rules).string = {min_len: 1, max_len: 200},
|
(validate.rules).string = {min_len: 1, max_len: 200},
|
||||||
@ -2168,6 +2238,41 @@ enum AuthenticationMethodType {
|
|||||||
AUTHENTICATION_METHOD_TYPE_OTP_EMAIL = 7;
|
AUTHENTICATION_METHOD_TYPE_OTP_EMAIL = 7;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message ListAuthenticationFactorsRequest{
|
||||||
|
string user_id = 1 [
|
||||||
|
(validate.rules).string = {min_len: 1, max_len: 200},
|
||||||
|
(google.api.field_behavior) = REQUIRED,
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
min_length: 1;
|
||||||
|
max_length: 200;
|
||||||
|
example: "\"69629026806489455\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
repeated AuthFactors auth_factors = 2 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "Specify the Auth Factors you are interested in"
|
||||||
|
default: "All Auth Factors"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
repeated AuthFactorState states = 3 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "Specify the state of the Auth Factors"
|
||||||
|
default: "Auth Factors that are ready"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
enum AuthFactors {
|
||||||
|
OTP = 0;
|
||||||
|
OTP_SMS = 1;
|
||||||
|
OTP_EMAIL = 2;
|
||||||
|
U2F = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ListAuthenticationFactorsResponse {
|
||||||
|
repeated zitadel.user.v2.AuthFactor result = 1;
|
||||||
|
}
|
||||||
|
|
||||||
message CreateInviteCodeRequest {
|
message CreateInviteCodeRequest {
|
||||||
string user_id = 1 [
|
string user_id = 1 [
|
||||||
(validate.rules).string = {min_len: 1, max_len: 200},
|
(validate.rules).string = {min_len: 1, max_len: 200},
|
||||||
|
@ -30,6 +30,7 @@ message SearchQuery {
|
|||||||
NotQuery not_query = 13;
|
NotQuery not_query = 13;
|
||||||
InUserEmailsQuery in_user_emails_query = 14;
|
InUserEmailsQuery in_user_emails_query = 14;
|
||||||
OrganizationIdQuery organization_id_query = 15;
|
OrganizationIdQuery organization_id_query = 15;
|
||||||
|
PhoneQuery phone_query = 16;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -184,6 +185,26 @@ message EmailQuery {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Query for users with a specific phone.
|
||||||
|
message PhoneQuery {
|
||||||
|
string number = 1 [
|
||||||
|
(validate.rules).string = {max_len: 20},
|
||||||
|
(google.api.field_behavior) = REQUIRED,
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "Phone number of the user"
|
||||||
|
min_length: 1;
|
||||||
|
max_length: 20;
|
||||||
|
example: "\"+41791234567\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
zitadel.object.v2beta.TextQueryMethod method = 2 [
|
||||||
|
(validate.rules).enum.defined_only = true,
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "defines which text equality method is used";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
// Query for users with a specific state.
|
// Query for users with a specific state.
|
||||||
message LoginNameQuery {
|
message LoginNameQuery {
|
||||||
string login_name = 1 [
|
string login_name = 1 [
|
||||||
|
Loading…
x
Reference in New Issue
Block a user