You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@streampipes.apache.org by ri...@apache.org on 2021/10/01 17:56:12 UTC

[incubator-streampipes] branch STREAMPIPES-426 updated: [STREAMPIPES-434] Add auto-setup and service account model

This is an automated email from the ASF dual-hosted git repository.

riemer pushed a commit to branch STREAMPIPES-426
in repository https://gitbox.apache.org/repos/asf/incubator-streampipes.git


The following commit(s) were added to refs/heads/STREAMPIPES-426 by this push:
     new a35c731  [STREAMPIPES-434] Add auto-setup and service account model
a35c731 is described below

commit a35c73185c5421bcc50f05bade1c1a1e11661b76
Author: Dominik Riemer <ri...@fzi.de>
AuthorDate: Fri Oct 1 19:56:02 2021 +0200

    [STREAMPIPES-434] Add auto-setup and service account model
---
 pom.xml                                            |  23 +-
 .../backend/StreamPipesBackendApplication.java     |  21 ++
 .../apache/streampipes/commons/constants/Envs.java |  11 +-
 .../commons/constants/InstallationConstants.java   |  25 +-
 .../streampipes/config/backend/BackendConfig.java  |  10 +
 .../config/backend/BackendConfigKeys.java          |   1 +
 streampipes-model-client/pom.xml                   |   4 +
 .../model/client/setup/InitialSettings.java        |  46 ++-
 .../streampipes/model/client/user/Authc.java       |  44 ---
 .../streampipes/model/client/user/Authz.java       |  47 ---
 .../apache/streampipes/model/client/user/Info.java |  51 ---
 ...uthenticationRequest.java => LoginRequest.java} |   6 +-
 .../streampipes/model/client/user/Principal.java   | 188 +++++++++--
 .../user/{Credentials.java => PrincipalType.java}  |  20 +-
 .../model/client/user/ServiceAccount.java          |  68 ++++
 .../client/user/ShiroAuthenticationResponse.java   |  65 ----
 .../user/ShiroAuthenticationResponseFactory.java   |  40 ---
 .../apache/streampipes/model/client/user/User.java | 266 ----------------
 .../streampipes/model/client/user/UserAccount.java | 185 +++++++++++
 .../manager/setup/AutoInstallation.java            | 119 +++++++
 .../manager/setup/CouchDbInstallationStep.java     | 346 +++++++++++----------
 .../manager/setup/InstallationConfiguration.java   |   8 +-
 .../manager/setup/InstallationStep.java            |  33 +-
 .../streampipes/manager/setup/Installer.java       |  52 ----
 .../setup/PipelineElementInstallationStep.java     |  19 +-
 .../setup/UserRegistrationInstallationStep.java    |  52 +++-
 .../manager/storage/UserManagementService.java     |   8 +-
 .../streampipes/manager/storage/UserService.java   |  79 ++---
 .../rest/filter/TokenAuthenticationFilter.java     |   3 +-
 .../streampipes/rest/impl/Authentication.java      |   7 +-
 .../org/apache/streampipes/rest/impl/Setup.java    |  23 +-
 .../apache/streampipes/rest/impl/UserProfile.java  |  13 +-
 streampipes-security-jwt/pom.xml                   |  34 ++
 .../streampipes/security/jwt/JwtTokenUtils.java    |  78 +++++
 .../serializers/json/GsonSerializer.java           |  14 +-
 .../serializers/json/PrincipalDeserializer.java    |  67 ++++
 .../streampipes/storage/api/IUserStorage.java      |  20 +-
 .../storage/couchdb/impl/UserStorage.java          |  55 +++-
 .../streampipes/storage/couchdb/utils/Utils.java   |  10 +-
 streampipes-user-management/pom.xml                |   5 +
 .../user/management/jwt/JwtTokenProvider.java      |  46 +--
 .../user/management/model/LocalUser.java           |  82 -----
 .../management/service/SpUserDetailsService.java   |   6 +-
 .../user/management/service/TokenService.java      |  24 +-
 .../app/core-model/gen/streampipes-model-client.ts |  97 ++++--
 .../apis/pipeline-template.service.ts              |   4 +-
 .../profile/components/basic-profile-settings.ts   |   4 +-
 ui/src/app/profile/profile.module.ts               |   2 +
 ui/src/app/profile/profile.service.ts              |   8 +-
 49 files changed, 1315 insertions(+), 1124 deletions(-)

diff --git a/pom.xml b/pom.xml
index 43a8c62..859d485 100644
--- a/pom.xml
+++ b/pom.xml
@@ -102,6 +102,7 @@
         <spark.version>2.1.2</spark.version>
         <spring.version>5.3.10</spring.version>
         <spring-boot.version>2.5.5</spring-boot.version>
+        <spring-security.version>5.5.2</spring-security.version>
         <swagger.version>2.1.6</swagger.version>
         <type-parser.version>0.6.0</type-parser.version>
         <underscore.version>1.47</underscore.version>
@@ -200,7 +201,7 @@
             <dependency>
                 <groupId>com.google.code.gson</groupId>
                 <artifactId>gson</artifactId>
-                <version>2.8.7</version>
+                <version>2.8.8</version>
             </dependency>
             <dependency>
                 <groupId>com.google.guava</groupId>
@@ -683,6 +684,11 @@
                 <version>${spring.version}</version>
             </dependency>
             <dependency>
+                <groupId>org.springframework.security</groupId>
+                <artifactId>spring-security-core</artifactId>
+                <version>${spring-security.version}</version>
+            </dependency>
+            <dependency>
                 <groupId>org.springframework</groupId>
                 <artifactId>spring-web</artifactId>
                 <version>${spring.version}</version>
@@ -832,12 +838,14 @@
         <module>streampipes-commons</module>
         <module>streampipes-config</module>
         <module>streampipes-connect</module>
+        <module>streampipes-connect-api</module>
         <module>streampipes-connect-container-master</module>
         <module>streampipes-connect-container-worker</module>
         <module>streampipes-container</module>
         <module>streampipes-container-base</module>
         <module>streampipes-container-extensions</module>
         <module>streampipes-container-standalone</module>
+        <module>streampipes-data-explorer</module>
         <module>streampipes-dataformat</module>
         <module>streampipes-dataformat-cbor</module>
         <module>streampipes-dataformat-fst</module>
@@ -858,9 +866,15 @@
         <module>streampipes-pipeline-management</module>
         <module>streampipes-sources</module>
         <module>streampipes-rest</module>
+        <module>streampipes-rest-core-base</module>
         <module>streampipes-rest-shared</module>
         <module>streampipes-sdk</module>
+        <module>streampipes-security-jwt</module>
         <module>streampipes-serializers-json</module>
+        <module>streampipes-service-discovery</module>
+        <module>streampipes-service-discovery-consul</module>
+        <module>streampipes-service-discovery-api</module>
+        <module>streampipes-service-extensions-base</module>
         <module>streampipes-storage-api</module>
         <module>streampipes-storage-management</module>
         <module>streampipes-storage-couchdb</module>
@@ -875,13 +889,6 @@
         <module>streampipes-wrapper-siddhi</module>
         <module>streampipes-wrapper-spark</module>
         <module>streampipes-wrapper-standalone</module>
-        <module>streampipes-data-explorer</module>
-        <module>streampipes-service-discovery</module>
-        <module>streampipes-service-discovery-consul</module>
-        <module>streampipes-service-discovery-api</module>
-        <module>streampipes-connect-api</module>
-        <module>streampipes-service-extensions-base</module>
-        <module>streampipes-rest-core-base</module>
     </modules>
 
     <profiles>
diff --git a/streampipes-backend/src/main/java/org/apache/streampipes/backend/StreamPipesBackendApplication.java b/streampipes-backend/src/main/java/org/apache/streampipes/backend/StreamPipesBackendApplication.java
index 3ece3e7..721c159 100644
--- a/streampipes-backend/src/main/java/org/apache/streampipes/backend/StreamPipesBackendApplication.java
+++ b/streampipes-backend/src/main/java/org/apache/streampipes/backend/StreamPipesBackendApplication.java
@@ -17,10 +17,12 @@
  */
 package org.apache.streampipes.backend;
 
+import org.apache.streampipes.config.backend.BackendConfig;
 import org.apache.streampipes.container.base.BaseNetworkingConfig;
 import org.apache.streampipes.container.base.StreamPipesServiceBase;
 import org.apache.streampipes.manager.health.PipelineHealthCheck;
 import org.apache.streampipes.manager.operations.Operations;
+import org.apache.streampipes.manager.setup.AutoInstallation;
 import org.apache.streampipes.model.pipeline.Pipeline;
 import org.apache.streampipes.model.pipeline.PipelineOperationStatus;
 import org.apache.streampipes.rest.notifications.NotificationListener;
@@ -90,12 +92,31 @@ public class StreamPipesBackendApplication extends StreamPipesServiceBase {
     this.executorService = Executors.newSingleThreadScheduledExecutor();
     this.healthCheckExecutorService = Executors.newSingleThreadScheduledExecutor();
 
+    if (!isConfigured()) {
+      doInitialSetup();
+    }
+
+    new NotificationListener().contextInitialized(null);
     executorService.schedule(this::startAllPreviouslyStoppedPipelines, 5, TimeUnit.SECONDS);
     LOG.info("Pipeline health check will run every {} seconds", HEALTH_CHECK_INTERVAL);
     healthCheckExecutorService.scheduleAtFixedRate(new PipelineHealthCheck(),
             HEALTH_CHECK_INTERVAL,
             HEALTH_CHECK_INTERVAL,
             HEALTH_CHECK_UNIT);
+
+  }
+
+  private boolean isConfigured() {
+    return BackendConfig.INSTANCE.isConfigured();
+  }
+
+  private void doInitialSetup() {
+    LOG.info("\n\n**********\n\nWelcome to Apache StreamPipes!\n\n**********\n\n");
+    LOG.info("We will perform the initial setup, grab some coffee and cross your fingers ;-)...");
+
+    BackendConfig.INSTANCE.updateSetupStatus(true);
+    new AutoInstallation().startAutoInstallation();
+    BackendConfig.INSTANCE.updateSetupStatus(false);
   }
 
   private void schedulePipelineStart(Pipeline pipeline, boolean restartOnReboot) {
diff --git a/streampipes-commons/src/main/java/org/apache/streampipes/commons/constants/Envs.java b/streampipes-commons/src/main/java/org/apache/streampipes/commons/constants/Envs.java
index edae3de..785282b 100644
--- a/streampipes-commons/src/main/java/org/apache/streampipes/commons/constants/Envs.java
+++ b/streampipes-commons/src/main/java/org/apache/streampipes/commons/constants/Envs.java
@@ -23,7 +23,12 @@ public enum Envs {
   SP_PORT("SP_PORT"),
   SP_CONSUL_LOCATION("CONSUL_LOCATION"),
   SP_KAFKA_RETENTION_MS("SP_KAFKA_RETENTION_MS"),
-  SP_JWT_SECRET("JWT_SECRET");
+  SP_JWT_SECRET("JWT_SECRET"),
+  SP_INITIAL_ADMIN_USER("SP_INITIAL_ADMIN_USER"),
+  SP_INITIAL_ADMIN_PASSWORD("SP_INITIAL_ADMIN_PASSWORD"),
+  SP_INITIAL_CLIENT_USER("SP_INITIAL_SERVICE_USER"),
+  SP_INITIAL_CLIENT_SECRET("SP_INITIAL_CLIENT_SECRET"),
+  SP_SETUP_INSTALL_PIPELINE_ELEMENTS("SP_SETUP_INSTALL_PIPELINE_ELEMENTS");
 
   private final String envVariableName;
 
@@ -47,4 +52,8 @@ public enum Envs {
     return CustomEnvs.getEnvAsBoolean(this.envVariableName);
   }
 
+  public String getEnvVariableName() {
+    return envVariableName;
+  }
+
 }
diff --git a/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/Credentials.java b/streampipes-commons/src/main/java/org/apache/streampipes/commons/constants/InstallationConstants.java
similarity index 63%
copy from streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/Credentials.java
copy to streampipes-commons/src/main/java/org/apache/streampipes/commons/constants/InstallationConstants.java
index 90956bf..42effdc 100644
--- a/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/Credentials.java
+++ b/streampipes-commons/src/main/java/org/apache/streampipes/commons/constants/InstallationConstants.java
@@ -15,24 +15,13 @@
  * limitations under the License.
  *
  */
+package org.apache.streampipes.commons.constants;
 
-package org.apache.streampipes.model.client.user;
+public class InstallationConstants {
 
-public class Credentials {
-
-	private String email;
-	
-	public Credentials(String email) {
-		super();
-		this.email = email;
-	}
-	
-	public String getEmail() {
-		return email;
-	}
-	public void setEmail(String email) {
-		this.email = email;
-	}
-	
-	
+  public static final String INITIAL_ADMIN_USER_DEFAULT = "admin";
+  public static final String INITIAL_ADMIN_PW_DEFAULT = "admin";
+  public static final String INITIAL_CLIENT_USER_DEFAULT = "sp-service-client";
+  public static final String INITIAL_CLIENT_SECRET_DEFAULT = "my-apache-streampipes-secret-key-change-me";
+  public static final boolean INSTALL_PIPELINE_ELEMENTS = true;
 }
diff --git a/streampipes-config/src/main/java/org/apache/streampipes/config/backend/BackendConfig.java b/streampipes-config/src/main/java/org/apache/streampipes/config/backend/BackendConfig.java
index bb0f678..52765cb 100644
--- a/streampipes-config/src/main/java/org/apache/streampipes/config/backend/BackendConfig.java
+++ b/streampipes-config/src/main/java/org/apache/streampipes/config/backend/BackendConfig.java
@@ -56,6 +56,8 @@ public enum BackendConfig {
     config.register(BackendConfigKeys.ELASTICSEARCH_PROTOCOL, "http", "Protocol the elasticsearch service");
     config.register(BackendConfigKeys.IS_CONFIGURED, false, "Boolean that indicates whether streampipes is " +
             "already configured or not");
+    config.register(BackendConfigKeys.IS_SETUP_RUNNING, false, "Boolean that indicates whether the initial setup " +
+            "is currently running");
     config.register(BackendConfigKeys.ASSETS_DIR, makeAssetLocation(), "The directory where " +
             "pipeline element assets are stored.");
     config.register(BackendConfigKeys.FILES_DIR, makeFileLocation(), "The directory where " +
@@ -236,6 +238,14 @@ public enum BackendConfig {
     return config.getObject(BackendConfigKeys.LOCAL_AUTH_CONFIG, LocalAuthConfig.class, LocalAuthConfig.fromDefaults(getJwtSecret()));
   }
 
+  public boolean isSetupRunning() {
+    return config.getBoolean(BackendConfigKeys.IS_SETUP_RUNNING);
+  }
+
+  public void updateSetupStatus(boolean status) {
+    config.setBoolean(BackendConfigKeys.IS_SETUP_RUNNING, status);
+  }
+
   private String getJwtSecret() {
     if (Envs.SP_JWT_SECRET.exists()) {
       return Envs.SP_JWT_SECRET.getValue();
diff --git a/streampipes-config/src/main/java/org/apache/streampipes/config/backend/BackendConfigKeys.java b/streampipes-config/src/main/java/org/apache/streampipes/config/backend/BackendConfigKeys.java
index 0aec6c8..9dbcc89 100644
--- a/streampipes-config/src/main/java/org/apache/streampipes/config/backend/BackendConfigKeys.java
+++ b/streampipes-config/src/main/java/org/apache/streampipes/config/backend/BackendConfigKeys.java
@@ -33,6 +33,7 @@ public class BackendConfigKeys {
   public static final String ELASTICSEARCH_PORT = "SP_ELASTICSEARCH_PORT";
   public static final String ELASTICSEARCH_PROTOCOL = "SP_ELASTICSEARCH_PROTOCOL";
   public static final String IS_CONFIGURED = "SP_IS_CONFIGURED";
+  public static final String IS_SETUP_RUNNING = "SP_IS_SETUP_RUNNING";
   public static final String KAFKA_REST_HOST = "SP_KAFKA_REST_HOST";
   public static final String KAFKA_REST_PORT = "SP_KAFKA_REST_PORT";
   public static final String ASSETS_DIR = "SP_ASSETS_DIR";
diff --git a/streampipes-model-client/pom.xml b/streampipes-model-client/pom.xml
index ef47910..658078e 100644
--- a/streampipes-model-client/pom.xml
+++ b/streampipes-model-client/pom.xml
@@ -48,6 +48,10 @@
 			<groupId>org.apache.commons</groupId>
 			<artifactId>commons-lang3</artifactId>
 		</dependency>
+		<dependency>
+			<groupId>org.springframework.security</groupId>
+			<artifactId>spring-security-core</artifactId>
+		</dependency>
 	</dependencies>
 
 	<build>
diff --git a/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/setup/InitialSettings.java b/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/setup/InitialSettings.java
index cfb6aa1..c79c09c 100644
--- a/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/setup/InitialSettings.java
+++ b/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/setup/InitialSettings.java
@@ -20,36 +20,24 @@ package org.apache.streampipes.model.client.setup;
 
 public class InitialSettings {
 
-
-	private String adminEmail;
+	private String adminUser;
 	private String adminPassword;
+	private String initialServiceAccountName;
+	private String initialServiceAccountSecret;
 	private Boolean installPipelineElements;
 
-	public InitialSettings(String adminEmail, String adminPassword, Boolean installPipelineElements) {
-		this.adminEmail = adminEmail;
-		this.adminPassword = adminPassword;
-		this.installPipelineElements = installPipelineElements;
-	}
-
 	public InitialSettings() {
-		// TODO Auto-generated constructor stub
+
 	}
 
 	public String getAdminPassword() {
 		return adminPassword;
 	}
+
 	public void setAdminPassword(String adminPassword) {
 		this.adminPassword = adminPassword;
 	}
 
-	public String getAdminEmail() {
-		return adminEmail;
-	}
-
-	public void setAdminEmail(String adminEmail) {
-		this.adminEmail = adminEmail;
-	}
-
 	public Boolean getInstallPipelineElements() {
 		return installPipelineElements;
 	}
@@ -57,4 +45,28 @@ public class InitialSettings {
 	public void setInstallPipelineElements(Boolean installPipelineElements) {
 		this.installPipelineElements = installPipelineElements;
 	}
+
+	public String getAdminUser() {
+		return adminUser;
+	}
+
+	public void setAdminUser(String adminUser) {
+		this.adminUser = adminUser;
+	}
+
+	public String getInitialServiceAccountName() {
+		return initialServiceAccountName;
+	}
+
+	public void setInitialServiceAccountName(String initialServiceAccountName) {
+		this.initialServiceAccountName = initialServiceAccountName;
+	}
+
+	public String getInitialServiceAccountSecret() {
+		return initialServiceAccountSecret;
+	}
+
+	public void setInitialServiceAccountSecret(String initialServiceAccountSecret) {
+		this.initialServiceAccountSecret = initialServiceAccountSecret;
+	}
 }
diff --git a/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/Authc.java b/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/Authc.java
deleted file mode 100644
index 3e0be15..0000000
--- a/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/Authc.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package org.apache.streampipes.model.client.user;
-
-public class Authc {
-
-	private Principal principal;
-	private Credentials credentials;
-	
-	public Authc(Principal principal, Credentials credentials) {
-		super();
-		this.principal = principal;
-		this.credentials = credentials;
-	}
-	
-	public Principal getPrincipal() {
-		return principal;
-	}
-	public void setPrincipal(Principal principal) {
-		this.principal = principal;
-	}
-	public Credentials getCredentials() {
-		return credentials;
-	}
-	public void setCredentials(Credentials credentials) {
-		this.credentials = credentials;
-	}
-}
diff --git a/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/Authz.java b/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/Authz.java
deleted file mode 100644
index 587681d..0000000
--- a/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/Authz.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package org.apache.streampipes.model.client.user;
-
-import java.util.List;
-
-public class Authz {
-
-	private List<String> roles;
-	private List<String> permissions;
-	
-	public Authz(List<String> roles, List<String> permissions) {
-		super();
-		this.roles = roles;
-		this.permissions = permissions;
-	}
-	
-	public List<String> getRoles() {
-		return roles;
-	}
-	public void setRoles(List<String> roles) {
-		this.roles = roles;
-	}
-	public List<String> getPermissions() {
-		return permissions;
-	}
-	public void setPermissions(List<String> permissions) {
-		this.permissions = permissions;
-	}
-	
-}
diff --git a/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/Info.java b/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/Info.java
deleted file mode 100644
index b934c50..0000000
--- a/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/Info.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package org.apache.streampipes.model.client.user;
-
-public class Info {
-
-	private Authc authc;
-	private Authz authz;
-	
-	public Info(Authc authc, Authz authz) {
-		super();
-		this.authc = authc;
-		this.authz = authz;
-	}
-
-	public Info() {
-		// TODO Auto-generated constructor stub
-	}
-
-	public Authc getAuthc() {
-		return authc;
-	}
-
-	public void setAuthc(Authc authc) {
-		this.authc = authc;
-	}
-
-	public Authz getAuthz() {
-		return authz;
-	}
-
-	public void setAuthz(Authz authz) {
-		this.authz = authz;
-	}	
-}
diff --git a/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/ShiroAuthenticationRequest.java b/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/LoginRequest.java
similarity index 89%
rename from streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/ShiroAuthenticationRequest.java
rename to streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/LoginRequest.java
index cd620a5..9394859 100644
--- a/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/ShiroAuthenticationRequest.java
+++ b/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/LoginRequest.java
@@ -18,16 +18,16 @@
 
 package org.apache.streampipes.model.client.user;
 
-public class ShiroAuthenticationRequest {
+public class LoginRequest {
 
 	private String username;
 	private String password;
 
-	public ShiroAuthenticationRequest() {
+	public LoginRequest() {
 
 	}
 	
-	public ShiroAuthenticationRequest(String username, String password) {
+	public LoginRequest(String username, String password) {
 		super();
 		this.username = username;
 		this.password = password;
diff --git a/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/Principal.java b/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/Principal.java
index fc07425..29d20e0 100644
--- a/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/Principal.java
+++ b/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/Principal.java
@@ -18,31 +18,183 @@
 
 package org.apache.streampipes.model.client.user;
 
-public class Principal {
+import com.google.gson.annotations.SerializedName;
+import org.springframework.security.core.userdetails.UserDetails;
 
-	private String email;
-	private String apiKey;
-	
-	public Principal(String email, String apiKey) {
-		super();
-		this.email = email;
-		this.apiKey = apiKey;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+public abstract class Principal implements UserDetails {
+
+	protected @SerializedName("_id") String principalId;
+	protected @SerializedName("_rev") String rev;
+
+	private boolean accountEnabled;
+	private boolean accountLocked;
+	private boolean accountExpired;
+
+	private String principalName;
+
+	protected List<Element> ownSources;
+	protected List<Element> ownSepas;
+	protected List<Element> ownActions;
+
+	protected Set<Role> roles;
+
+	private PrincipalType principalType;
+
+	public Principal(PrincipalType principalType) {
+		this.principalType = principalType;
+		this.ownActions = new ArrayList<>();
+		this.ownSepas = new ArrayList<>();
+		this.ownSources = new ArrayList<>();
+		this.roles = new HashSet<>();
+	}
+
+	public List<Element> getOwnSources() {
+		return ownSources;
+	}
+
+	public void addOwnSource(String source, boolean publicElement) {
+		if (this.ownSources == null) this.ownSources = new ArrayList<>();
+		this.ownSources.add(new Element(source, publicElement));
+	}
+
+	public List<Element> getOwnSepas() {
+		return ownSepas;
+	}
+
+	public void addOwnSepa(String sepa, boolean publicElement) {
+		if (this.ownSepas == null) this.ownSepas = new ArrayList<>();
+		this.ownSepas.add(new Element(sepa, publicElement));
+	}
+
+	public List<Element> getOwnActions() {
+		return ownActions;
 	}
-	
 
-	public String getApiKey() {
-		return apiKey;
+	public void addOwnAction(String action, boolean publicElement) {
+		this.ownActions.add(new Element(action, publicElement));
 	}
-	public void setApiKey(String apiKey) {
-		this.apiKey = apiKey;
+
+	public void removeAction(String action) {
+		this.ownActions.remove(find(action, ownActions));
+	}
+
+	public void removeSepa(String sepa) {
+		this.ownSepas.remove(find(sepa, ownSepas));
+	}
+
+	public void removeSource(String source) {
+		this.ownSources.remove(find(source, ownSources));
+	}
+
+	public String getRev() {
+		return rev;
+	}
+
+	public void setRev(String rev) {
+		this.rev = rev;
 	}
 
-	public String getEmail() {
-		return email;
+	public String getPrincipalId() {
+		return principalId;
 	}
 
-	public void setEmail(String email) {
-		this.email = email;
+	public void setPrincipalId(String principalId) {
+		this.principalId = principalId;
 	}
-	
+
+	private Element find(String elementId, List<Element> source) {
+		return source.stream().filter(f -> f.getElementId().equals(elementId)).findFirst().orElseThrow(IllegalArgumentException::new);
+	}
+
+	public boolean isAccountEnabled() {
+		return accountEnabled;
+	}
+
+	public void setAccountEnabled(boolean accountEnabled) {
+		this.accountEnabled = accountEnabled;
+	}
+
+	public boolean isAccountLocked() {
+		return accountLocked;
+	}
+
+	public void setAccountLocked(boolean accountLocked) {
+		this.accountLocked = accountLocked;
+	}
+
+	public boolean isAccountExpired() {
+		return accountExpired;
+	}
+
+	public void setAccountExpired(boolean accountExpired) {
+		this.accountExpired = accountExpired;
+	}
+
+	public String getPrincipalName() {
+		return principalName;
+	}
+
+	public void setPrincipalName(String principalName) {
+		this.principalName = principalName;
+	}
+
+	public void setOwnSources(List<Element> ownSources) {
+		this.ownSources = ownSources;
+	}
+
+	public void setOwnSepas(List<Element> ownSepas) {
+		this.ownSepas = ownSepas;
+	}
+
+	public void setOwnActions(List<Element> ownActions) {
+		this.ownActions = ownActions;
+	}
+
+	public Set<Role> getRoles() {
+		return roles;
+	}
+
+	public void setRoles(Set<Role> roles) {
+		this.roles = roles;
+	}
+
+	public PrincipalType getPrincipalType() {
+		return principalType;
+	}
+
+	public void setPrincipalType(PrincipalType principalType) {
+		this.principalType = principalType;
+	}
+
+	@Override
+	public boolean isAccountNonExpired() {
+		return !this.isAccountExpired();
+	}
+
+	@Override
+	public boolean isAccountNonLocked() {
+		return !this.isAccountLocked();
+	}
+
+	@Override
+	public boolean isCredentialsNonExpired() {
+		return true;
+	}
+
+	@Override
+	public boolean isEnabled() {
+		return this.isAccountEnabled();
+	}
+
+	@Override
+	public String getUsername() {
+		return principalName;
+	}
+
+
 }
diff --git a/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/Credentials.java b/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/PrincipalType.java
similarity index 78%
rename from streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/Credentials.java
rename to streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/PrincipalType.java
index 90956bf..be84b20 100644
--- a/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/Credentials.java
+++ b/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/PrincipalType.java
@@ -15,24 +15,10 @@
  * limitations under the License.
  *
  */
-
 package org.apache.streampipes.model.client.user;
 
-public class Credentials {
+public enum PrincipalType {
 
-	private String email;
-	
-	public Credentials(String email) {
-		super();
-		this.email = email;
-	}
-	
-	public String getEmail() {
-		return email;
-	}
-	public void setEmail(String email) {
-		this.email = email;
-	}
-	
-	
+  USER_ACCOUNT,
+  SERVICE_ACCOUNT
 }
diff --git a/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/ServiceAccount.java b/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/ServiceAccount.java
new file mode 100644
index 0000000..7fafdb5
--- /dev/null
+++ b/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/ServiceAccount.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.streampipes.model.client.user;
+
+import org.springframework.security.core.GrantedAuthority;
+
+import java.util.Collection;
+import java.util.Set;
+
+public class ServiceAccount extends Principal {
+
+  private String clientSecret;
+
+  public static ServiceAccount from(String serviceAccountName,
+                                 String clientSecret,
+                                 Set<Role> roles) {
+    ServiceAccount account = new ServiceAccount();
+    account.setPrincipalName(serviceAccountName);
+    account.setClientSecret(clientSecret);
+    account.setRoles(roles);
+    account.setAccountEnabled(true);
+    account.setAccountLocked(false);
+
+    return account;
+  }
+
+  public ServiceAccount() {
+    super(PrincipalType.SERVICE_ACCOUNT);
+  }
+
+  public String getClientSecret() {
+    return clientSecret;
+  }
+
+  public void setClientSecret(String clientSecret) {
+    this.clientSecret = clientSecret;
+  }
+
+  @Override
+  public Collection<? extends GrantedAuthority> getAuthorities() {
+    return null;
+  }
+
+  @Override
+  public String getPassword() {
+    return null;
+  }
+
+  @Override
+  public String getUsername() {
+    return null;
+  }
+}
diff --git a/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/ShiroAuthenticationResponse.java b/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/ShiroAuthenticationResponse.java
deleted file mode 100644
index 5f09b30..0000000
--- a/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/ShiroAuthenticationResponse.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package org.apache.streampipes.model.client.user;
-
-public class ShiroAuthenticationResponse {
-
-	private Info info;
-	private boolean success;
-	private String callbackUrl;
-	private String token;
-	
-	public ShiroAuthenticationResponse(Info info) {
-		super();
-		this.info = info;
-		this.success = true;
-	}
-
-	public Info getInfo() {
-		return info;
-	}
-
-	public void setInfo(Info info) {
-		this.info = info;
-	}
-
-	public boolean isSuccess() {
-		return success;
-	}
-
-	public void setSuccess(boolean success) {
-		this.success = success;
-	}
-
-	public String getCallbackUrl() {
-		return callbackUrl;
-	}
-
-	public void setCallbackUrl(String callbackUrl) {
-		this.callbackUrl = callbackUrl;
-	}
-
-	public String getToken() {
-		return token;
-	}
-
-	public void setToken(String token) {
-		this.token = token;
-	}
-}
diff --git a/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/ShiroAuthenticationResponseFactory.java b/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/ShiroAuthenticationResponseFactory.java
deleted file mode 100644
index 57a8fcf..0000000
--- a/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/ShiroAuthenticationResponseFactory.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package org.apache.streampipes.model.client.user;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class ShiroAuthenticationResponseFactory {
-
-	public static ShiroAuthenticationResponse create(User user)
-	{
-		Authc authc = new Authc(new Principal(user.getEmail(), ""), new Credentials(user.getEmail()));
-		List<String> roles = new ArrayList<>();
-		user.getRoles().forEach(r -> roles.add(r.toString()));
-		Authz authz = new Authz(roles, new ArrayList<>());
-		
-		Info info = new Info();
-		info.setAuthc(authc);
-		info.setAuthz(authz);
-		ShiroAuthenticationResponse response = new ShiroAuthenticationResponse(info);
-		
-		return response;
-	}
-}
diff --git a/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/User.java b/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/User.java
deleted file mode 100644
index aa76714..0000000
--- a/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/User.java
+++ /dev/null
@@ -1,266 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package org.apache.streampipes.model.client.user;
-
-import com.google.gson.annotations.SerializedName;
-import org.apache.streampipes.model.shared.annotation.TsModel;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Set;
-
-@TsModel
-public class User {
-
-	protected @SerializedName("_id") String userId;
-	protected @SerializedName("_rev") String rev;
-	protected String email;
-
-	protected String username;
-	protected String fullName;
-	protected String password;
-
-	protected List<Element> ownSources;
-	protected List<Element> ownSepas;
-	protected List<Element> ownActions;
-
-	protected List<String> preferredSources;
-	protected List<String> preferredSepas;
-	protected List<String> preferredActions;
-
-	protected List<UserApiToken> userApiTokens;
-
-	protected boolean hideTutorial;
-	protected boolean darkMode = false;
-
-	protected Set<Role> roles;
-
-	public User() {
-		this.hideTutorial = false;
-		this.userApiTokens = new ArrayList<>();
-	}
-	
-	public User(String email, String password, Set<Role> roles, List<Element> ownSources, List<Element> ownSepas, List<Element> ownActions) {
-		super();
-		this.email = email;
-		
-		this.password = password;
-		this.roles = roles;
-		
-		this.ownSources = ownSources;
-		this.ownSepas = ownSepas;
-		this.ownActions = ownActions;
-
-		this.hideTutorial = false;
-	}
-	
-	public User(String email, String password, Set<Role> roles)
-	{ 
-		this.email = email;
-		this.password = password;
-		this.roles = roles;
-
-		this.ownActions = new ArrayList<>();
-		this.ownSepas = new ArrayList<>();
-		this.ownSources = new ArrayList<>();
-		
-		this.preferredActions = new ArrayList<>();
-		this.preferredSepas = new ArrayList<>();
-		this.preferredSources = new ArrayList<>();
-
-		this.hideTutorial = false;
-	}
-
-	public String getEmail() {
-		return email;
-	}
-
-	public void setEmail(String email) {
-		this.email = email;
-	}
-
-	public String getPassword() {
-		return password;
-	}
-
-	public void setPassword(String password) {
-		this.password = password;
-	}
-
-	public Set<Role> getRoles() {
-		return roles;
-	}
-
-	public void setRoles(Set<Role> roles) {
-		this.roles = roles;
-	}
-
-	public String getUserId() {
-		return userId;
-	}
-
-	public void setUserId(String userId) {
-		this.userId = userId;
-	}
-
-	public List<Element> getOwnSources() {
-		return ownSources;
-	}
-
-	public void addOwnSource(String source, boolean publicElement) {
-		if (this.ownSources == null) this.ownSources = new ArrayList<>();
-		this.ownSources.add(new Element(source, publicElement));
-	}
-
-	public List<Element> getOwnSepas() {
-		return ownSepas;
-	}
-
-	public void addOwnSepa(String sepa, boolean publicElement) {
-		if (this.ownSepas == null) this.ownSepas = new ArrayList<>();
-		this.ownSepas.add(new Element(sepa, publicElement));
-	}
-
-	public List<Element> getOwnActions() {
-		return ownActions;
-	}
-
-	public void addOwnAction(String action, boolean publicElement) {
-		this.ownActions.add(new Element(action, publicElement));
-	}
-
-	public void removeAction(String action) {
-		this.ownActions.remove(find(action, ownActions));
-	}
-
-	public void removeSepa(String sepa) {
-		this.ownSepas.remove(find(sepa, ownSepas));
-	}
-
-	public void removeSource(String source) {
-		this.ownSources.remove(find(source, ownSources));
-	}
-	
-	public List<String> getPreferredSources() {
-		return preferredSources;
-	}
-
-	public void setPreferredSources(List<String> preferredSources) {
-		this.preferredSources = preferredSources;
-	}
-
-	public List<String> getPreferredSepas() {
-		return preferredSepas;
-	}
-
-	public void setPreferredSepas(List<String> preferredSepas) {
-		this.preferredSepas = preferredSepas;
-	}
-
-	public List<String> getPreferredActions() {
-		return preferredActions;
-	}
-
-	public void setPreferredActions(List<String> preferredActions) {
-		this.preferredActions = preferredActions;
-	}
-	
-	public void addPreferredSource(String elementId)
-	{
-		this.preferredSources.add(elementId);
-	}
-	
-	public void addPreferredSepa(String elementId)
-	{
-		this.preferredSepas.add(elementId);
-	}
-	
-	public void addPreferredAction(String elementId)
-	{
-		this.preferredActions.add(elementId);
-	}
-	
-	public void removePreferredSource(String elementId)
-	{
-		this.preferredSources.remove(elementId);
-	}
-	
-	public void removePreferredSepa(String elementId)
-	{
-		this.preferredSepas.remove(elementId);
-	}
-	
-	public void removePreferredAction(String elementId)
-	{
-		this.preferredActions.remove(elementId);
-	}
-
-	public String getUsername() {
-		return username;
-	}
-
-	public void setUsername(String username) {
-		this.username = username;
-	}
-
-	public String getFullName() {
-		return fullName;
-	}
-
-	public void setFullName(String fullName) {
-		this.fullName = fullName;
-	}
-
-	public List<UserApiToken> getUserApiTokens() {
-		return userApiTokens;
-	}
-
-	public void setUserApiTokens(List<UserApiToken> userApiTokens) {
-		this.userApiTokens = userApiTokens;
-	}
-
-	private Element find(String elementId, List<Element> source)
-	{
-		return source.stream().filter(f -> f.getElementId().equals(elementId)).findFirst().orElseThrow(IllegalArgumentException::new);
-	}
-
-	public boolean isHideTutorial() {
-		return hideTutorial;
-	}
-
-	public void setHideTutorial(boolean hideTutorial) {
-		this.hideTutorial = hideTutorial;
-	}
-
-	public String getRev() {
-		return rev;
-	}
-
-	public void setRev(String rev) {
-		this.rev = rev;
-	}
-
-	public boolean isDarkMode() {
-		return darkMode;
-	}
-
-	public void setDarkMode(boolean darkMode) {
-		this.darkMode = darkMode;
-	}
-}
diff --git a/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/UserAccount.java b/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/UserAccount.java
new file mode 100644
index 0000000..c082dd6
--- /dev/null
+++ b/streampipes-model-client/src/main/java/org/apache/streampipes/model/client/user/UserAccount.java
@@ -0,0 +1,185 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.streampipes.model.client.user;
+
+import org.apache.streampipes.model.shared.annotation.TsModel;
+import org.springframework.security.core.GrantedAuthority;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+
+@TsModel
+public class UserAccount extends Principal {
+
+	protected String email;
+	protected String fullName;
+	protected String username;
+	protected String password;
+
+	protected List<String> preferredDataStreams;
+	protected List<String> preferredDataProcessors;
+	protected List<String> preferredDataSinks;
+
+	protected List<UserApiToken> userApiTokens;
+
+	protected boolean hideTutorial;
+	protected boolean darkMode = false;
+
+	public static UserAccount from(String username,
+																 String encryptedPassword,
+																 Set<Role> roles) {
+		UserAccount account = new UserAccount();
+		account.setPrincipalName(username);
+		account.setPassword(encryptedPassword);
+		account.setRoles(roles);
+		account.setAccountEnabled(true);
+		account.setAccountLocked(false);
+
+		return account;
+	}
+
+	public UserAccount() {
+		super(PrincipalType.USER_ACCOUNT);
+		this.hideTutorial = false;
+		this.userApiTokens = new ArrayList<>();
+		this.preferredDataProcessors = new ArrayList<>();
+		this.preferredDataSinks = new ArrayList<>();
+		this.preferredDataStreams = new ArrayList<>();
+	}
+
+	public String getEmail() {
+		return email;
+	}
+
+	public void setEmail(String email) {
+		this.email = email;
+	}
+
+	public void setPassword(String password) {
+		this.password = password;
+	}
+
+	public Set<Role> getRoles() {
+		return roles;
+	}
+
+	public void setRoles(Set<Role> roles) {
+		this.roles = roles;
+	}
+
+	public List<String> getPreferredDataStreams() {
+		return preferredDataStreams;
+	}
+
+	public void setPreferredDataStreams(List<String> preferredDataStreams) {
+		this.preferredDataStreams = preferredDataStreams;
+	}
+
+	public List<String> getPreferredDataProcessors() {
+		return preferredDataProcessors;
+	}
+
+	public void setPreferredDataProcessors(List<String> preferredDataProcessors) {
+		this.preferredDataProcessors = preferredDataProcessors;
+	}
+
+	public List<String> getPreferredDataSinks() {
+		return preferredDataSinks;
+	}
+
+	public void setPreferredDataSinks(List<String> preferredDataSinks) {
+		this.preferredDataSinks = preferredDataSinks;
+	}
+	
+	public void addPreferredDataStream(String elementId)
+	{
+		this.preferredDataStreams.add(elementId);
+	}
+	
+	public void addPreferredDataProcessor(String elementId)
+	{
+		this.preferredDataProcessors.add(elementId);
+	}
+	
+	public void addPreferredDataSink(String elementId)
+	{
+		this.preferredDataSinks.add(elementId);
+	}
+	
+	public void removePreferredDataStream(String elementId) {
+		this.preferredDataStreams.remove(elementId);
+	}
+	
+	public void removePreferredDataProcessor(String elementId)
+	{
+		this.preferredDataProcessors.remove(elementId);
+	}
+	
+	public void removePreferredDataSink(String elementId)
+	{
+		this.preferredDataSinks.remove(elementId);
+	}
+
+	public String getFullName() {
+		return fullName;
+	}
+
+	public void setFullName(String fullName) {
+		this.fullName = fullName;
+	}
+
+	public List<UserApiToken> getUserApiTokens() {
+		return userApiTokens;
+	}
+
+	public void setUserApiTokens(List<UserApiToken> userApiTokens) {
+		this.userApiTokens = userApiTokens;
+	}
+
+	public boolean isHideTutorial() {
+		return hideTutorial;
+	}
+
+	public void setHideTutorial(boolean hideTutorial) {
+		this.hideTutorial = hideTutorial;
+	}
+
+
+	public boolean isDarkMode() {
+		return darkMode;
+	}
+
+	public void setDarkMode(boolean darkMode) {
+		this.darkMode = darkMode;
+	}
+
+	@Override
+	public Collection<? extends GrantedAuthority> getAuthorities() {
+		return null;
+	}
+
+	@Override
+	public String getPassword() {
+		return password;
+	}
+
+
+}
diff --git a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/setup/AutoInstallation.java b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/setup/AutoInstallation.java
new file mode 100644
index 0000000..a331027
--- /dev/null
+++ b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/setup/AutoInstallation.java
@@ -0,0 +1,119 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.streampipes.manager.setup;
+
+import org.apache.streampipes.commons.constants.CustomEnvs;
+import org.apache.streampipes.commons.constants.Envs;
+import org.apache.streampipes.commons.constants.InstallationConstants;
+import org.apache.streampipes.config.backend.BackendConfig;
+import org.apache.streampipes.model.client.setup.InitialSettings;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class AutoInstallation {
+
+  private static final Logger LOG = LoggerFactory.getLogger(AutoInstallation.class);
+
+  public void startAutoInstallation() {
+    InitialSettings settings = collectInitialSettings();
+
+    List<InstallationStep> steps = InstallationConfiguration.getInstallationSteps(settings);
+    AtomicInteger errorCount = new AtomicInteger();
+
+    steps.forEach(step -> {
+      step.install();
+      errorCount.addAndGet(step.getErrorCount());
+    });
+    if (errorCount.get() > 0) {
+      LOG.error("{} errors occurred during the setup process", errorCount);
+    } else {
+      BackendConfig.INSTANCE.setIsConfigured(true);
+      LOG.info("Initial setup completed successfully - you can now open the login page in the browser.");
+    }
+    LOG.info("\n\n**********\n\nAuto-Setup finished\n\n**********\n\n");
+  }
+
+  private InitialSettings collectInitialSettings() {
+    InitialSettings settings = new InitialSettings();
+    settings.setInstallPipelineElements(autoInstallPipelineElements());
+    settings.setAdminUser(findAdminUser());
+    settings.setAdminPassword(findAdminPassword());
+    settings.setInitialServiceAccountName(findServiceAccountName());
+    settings.setInitialServiceAccountSecret(findServiceAccountSecret());
+
+    return settings;
+  }
+
+  private boolean autoInstallPipelineElements() {
+    if (Envs.SP_SETUP_INSTALL_PIPELINE_ELEMENTS.exists()) {
+      return Envs.SP_SETUP_INSTALL_PIPELINE_ELEMENTS.getValueAsBoolean();
+    } else {
+      return InstallationConstants.INSTALL_PIPELINE_ELEMENTS;
+    }
+  }
+
+  private String findServiceAccountSecret() {
+    return getStringOrDefault(
+            Envs.SP_INITIAL_CLIENT_SECRET.getEnvVariableName(),
+            InstallationConstants.INITIAL_CLIENT_SECRET_DEFAULT
+    );
+  }
+
+  private String findServiceAccountName() {
+    return getStringOrDefault(
+            Envs.SP_INITIAL_CLIENT_USER.getEnvVariableName(),
+            InstallationConstants.INITIAL_CLIENT_USER_DEFAULT
+            );
+  }
+
+  private String findAdminUser() {
+    return getStringOrDefault(
+            Envs.SP_INITIAL_ADMIN_USER.getEnvVariableName(),
+            InstallationConstants.INITIAL_ADMIN_USER_DEFAULT
+    );
+  }
+
+  private String findAdminPassword() {
+    return getStringOrDefault(
+            Envs.SP_INITIAL_ADMIN_PASSWORD.getEnvVariableName(),
+            InstallationConstants.INITIAL_ADMIN_PW_DEFAULT
+    );
+  }
+
+  private String getStringOrDefault(String envVariable, String defaultValue) {
+    boolean exists = exists(envVariable);
+    if (exists) {
+      LOG.info("Using provided environment variable {}", envVariable);
+      return getString(envVariable);
+    } else {
+      LOG.info("Environment variable {} not found, using default value {}", envVariable, defaultValue);
+      return defaultValue;
+    }
+  }
+
+  private boolean exists(String envVariable) {
+    return CustomEnvs.exists(envVariable);
+  }
+
+  private String getString(String envVariable) {
+    return CustomEnvs.getEnv(envVariable);
+  }
+}
diff --git a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/setup/CouchDbInstallationStep.java b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/setup/CouchDbInstallationStep.java
index e481c14..7dbe871 100644
--- a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/setup/CouchDbInstallationStep.java
+++ b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/setup/CouchDbInstallationStep.java
@@ -19,201 +19,209 @@
 package org.apache.streampipes.manager.setup;
 
 import org.apache.streampipes.model.client.endpoint.ExtensionsServiceEndpoint;
-import org.apache.streampipes.model.message.Message;
-import org.apache.streampipes.model.message.Notifications;
 import org.apache.streampipes.storage.couchdb.impl.ExtensionsServiceEndpointStorageImpl;
 import org.apache.streampipes.storage.couchdb.utils.Utils;
 import org.lightcouch.DesignDocument;
 import org.lightcouch.DesignDocument.MapReduce;
 import org.lightcouch.Response;
 
-import java.util.*;
-
-public class CouchDbInstallationStep implements InstallationStep {
-
-    private static List<String> initRdfEndpointPorts =
-            Collections.singletonList("8099/api/v1/master/sources/");
-    private static final String initRdfEndpointHost = "http://localhost:";
-
-    private static final String PREPARING_NOTIFICATIONS_TEXT = "Preparing database " +
-            "'notifications'...";
-    private static final String PREPARING_USERS_TEXT = "Preparing database 'users'...";
-
-    public CouchDbInstallationStep() {
-
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class CouchDbInstallationStep extends InstallationStep {
+
+  private static final String initRdfEndpointHost = "http://localhost:";
+  private static final String PREPARING_NOTIFICATIONS_TEXT = "Preparing database " +
+          "'notifications'...";
+  private static final String PREPARING_USERS_TEXT = "Preparing database 'users'...";
+  private static List<String> initRdfEndpointPorts =
+          Collections.singletonList("8099/api/v1/master/sources/");
+
+  public CouchDbInstallationStep() {
+
+  }
+
+  @Override
+  public void install() {
+    createDatabases();
+    createViews();
+    addRdfEndpoints();
+  }
+
+  @Override
+  public String getTitle() {
+    return "Creating databases...";
+  }
+
+  private void createDatabases() {
+    try {
+      // Set up couchdb internal databases
+      Utils.getCouchDbInternalUsersClient();
+      Utils.getCouchDbReplicatorClient();
+      Utils.getCouchDbGlobalChangesClient();
+
+      // Set up streampipes internal databases
+      Utils.getCouchDbUserClient();
+      Utils.getCouchDbMonitoringClient();
+      Utils.getCouchDbPipelineClient();
+      Utils.getCouchDbConnectionClient();
+      Utils.getCouchDbNotificationClient();
+      Utils.getCouchDbPipelineCategoriesClient();
+      Utils.getCouchDbVisualizationClient();
+      Utils.getCouchDbRdfEndpointClient();
+      Utils.getCouchDbDashboardClient();
+      Utils.getCouchDbDashboardWidgetClient();
+      Utils.getCouchDbLabelClient();
+      Utils.getCouchDbCategoryClient();
+
+      logSuccess(getTitle());
+    } catch (Exception e) {
+      logFailure(getTitle(), e);
     }
-
-    @Override
-    public List<Message> install() {
-        List<Message> msgs = new ArrayList<>();
-        msgs.addAll(createDatabases());
-        msgs.addAll(createViews());
-        msgs.add(addRdfEndpoints());
-        return msgs;
+  }
+
+  private void createViews() {
+    addUserView();
+    addConnectionView();
+    addNotificationView();
+    addLabelView();
+  }
+
+  private void addRdfEndpoints() {
+    ExtensionsServiceEndpointStorageImpl rdfEndpointStorage = new ExtensionsServiceEndpointStorageImpl();
+    initRdfEndpointPorts
+            .forEach(p -> rdfEndpointStorage
+                    .addExtensionsServiceEndpoint(new ExtensionsServiceEndpoint(initRdfEndpointHost + p)));
+
+    logSuccess("Discovering pipeline element endpoints...");
+  }
+
+  private void addNotificationView() {
+    try {
+      DesignDocument userDocument = prepareDocument("_design/notificationtypes");
+      DesignDocument notificationCountDocument = prepareDocument("_design/unread");
+
+      Map<String, MapReduce> notificationTypeViews = new HashMap<>();
+      MapReduce notificationTypeFunction = new MapReduce();
+      notificationTypeFunction.setMap("function (doc) { var vizName = doc.title.replace(/\\s/g, '-'); var indexName = doc.correspondingPipelineId + '-' + vizName; emit([indexName, doc.createdAtTimestamp], doc);}");
+      notificationTypeViews.put("notificationtypes", notificationTypeFunction);
+      userDocument.setViews(notificationTypeViews);
+      Response resp = Utils.getCouchDbNotificationClient().design().synchronizeWithDb(userDocument);
+
+      Map<String, MapReduce> notificationCountTypeViews = new HashMap<>();
+      MapReduce countFunction = new MapReduce();
+      countFunction.setMap("function (doc) {\n" +
+              "  var user = doc.targetedAt; \n" +
+              "  if (!doc.read) {\n" +
+              "    emit(user, 1);\n" +
+              "  }\n" +
+              "}");
+      countFunction.setReduce("function (keys, values, rereduce) {\n" +
+              "  if (rereduce) {\n" +
+              "    return sum(values);\n" +
+              "  } else {\n" +
+              "    return values.length;\n" +
+              "  }\n" +
+              "}");
+      notificationCountTypeViews.put("unread", countFunction);
+      notificationCountDocument.setViews(notificationCountTypeViews);
+      Response countResp =
+              Utils.getCouchDbNotificationClient().design().synchronizeWithDb(notificationCountDocument);
+
+      if (resp.getError() != null && countResp != null) {
+        logFailure(PREPARING_NOTIFICATIONS_TEXT);
+      } else {
+        logSuccess(PREPARING_NOTIFICATIONS_TEXT);
+      }
+    } catch (Exception e) {
+      logFailure(PREPARING_NOTIFICATIONS_TEXT, e);
     }
+  }
 
-    @Override
-    public String getTitle() {
-        return "Creating databases...";
-    }
+  private void addUserView() {
+    try {
+      DesignDocument userDocument = prepareDocument("_design/users");
+      Map<String, MapReduce> views = new HashMap<>();
 
-    private List<Message> createDatabases() {
-        try {
-            // Set up couchdb internal databases
-            Utils.getCouchDbInternalUsersClient();
-            Utils.getCouchDbReplicatorClient();
-            Utils.getCouchDbGlobalChangesClient();
-
-            // Set up streampipes internal databases
-            Utils.getCouchDbUserClient();
-            Utils.getCouchDbMonitoringClient();
-            Utils.getCouchDbPipelineClient();
-            Utils.getCouchDbConnectionClient();
-            Utils.getCouchDbNotificationClient();
-            Utils.getCouchDbPipelineCategoriesClient();
-            Utils.getCouchDbVisualizationClient();
-            Utils.getCouchDbRdfEndpointClient();
-            Utils.getCouchDbDashboardClient();
-            Utils.getCouchDbDashboardWidgetClient();
-            Utils.getCouchDbLabelClient();
-            Utils.getCouchDbCategoryClient();
-
-            return Collections.singletonList(Notifications.success(getTitle()));
-        } catch (Exception e) {
-            return Collections.singletonList(Notifications.error(getTitle()));
-        }
-    }
+      MapReduce passwordFunction = new MapReduce();
+      passwordFunction.setMap("function(doc) { if(doc.principalName && doc.principalType === 'USER_ACCOUNT' && doc.password) { emit(doc.principalName, doc.password); } }");
 
-    private List<Message> createViews() {
-        List<Message> result = new ArrayList<>();
-        result.add(addUserView());
-        result.add(addConnectionView());
-        result.add(addNotificationView());
-        result.add(addLabelView());
-        return result;
-    }
+      MapReduce usernameFunction = new MapReduce();
+      usernameFunction.setMap("function(doc) { if(doc.principalName) { emit(doc.principalName, doc); } }");
 
-    private Message addRdfEndpoints() {
-        ExtensionsServiceEndpointStorageImpl rdfEndpointStorage = new ExtensionsServiceEndpointStorageImpl();
-        initRdfEndpointPorts
-                .forEach(p -> rdfEndpointStorage
-                        .addExtensionsServiceEndpoint(new ExtensionsServiceEndpoint(initRdfEndpointHost + p)));
+      MapReduce tokenFunction = new MapReduce();
+      tokenFunction.setMap("function(doc) { if (doc.userApiTokens) { doc.userApiTokens.forEach(function(token) { emit(token.hashedToken, doc.email); });}}");
 
-        return Notifications.success("Discovering pipeline element endpoints...");
-    }
+      views.put("password", passwordFunction);
+      views.put("username", usernameFunction);
+      views.put("token", tokenFunction);
 
-    private Message addNotificationView() {
-        try {
-            DesignDocument userDocument = prepareDocument("_design/notificationtypes");
-            DesignDocument notificationCountDocument = prepareDocument("_design/unread");
-
-            Map<String, MapReduce> notificationTypeViews = new HashMap<>();
-            MapReduce notificationTypeFunction = new MapReduce();
-            notificationTypeFunction.setMap("function (doc) { var vizName = doc.title.replace(/\\s/g, '-'); var indexName = doc.correspondingPipelineId + '-' + vizName; emit([indexName, doc.createdAtTimestamp], doc);}");
-            notificationTypeViews.put("notificationtypes", notificationTypeFunction);
-            userDocument.setViews(notificationTypeViews);
-            Response resp = Utils.getCouchDbNotificationClient().design().synchronizeWithDb(userDocument);
-
-            Map<String, MapReduce> notificationCountTypeViews = new HashMap<>();
-            MapReduce countFunction = new MapReduce();
-            countFunction.setMap("function (doc) {\n" +
-                    "  var user = doc.targetedAt; \n" +
-                    "  if (!doc.read) {\n" +
-                    "    emit(user, 1);\n" +
-                    "  }\n" +
-                    "}");
-            countFunction.setReduce("function (keys, values, rereduce) {\n" +
-                    "  if (rereduce) {\n" +
-                    "    return sum(values);\n" +
-                    "  } else {\n" +
-                    "    return values.length;\n" +
-                    "  }\n" +
-                    "}");
-            notificationCountTypeViews.put("unread", countFunction);
-            notificationCountDocument.setViews(notificationCountTypeViews);
-            Response countResp =
-                    Utils.getCouchDbNotificationClient().design().synchronizeWithDb(notificationCountDocument);
-
-            if (resp.getError() != null && countResp != null) return Notifications.error(PREPARING_NOTIFICATIONS_TEXT);
-            else return Notifications.success(PREPARING_NOTIFICATIONS_TEXT);
-        } catch (Exception e) {
-            return Notifications.error(PREPARING_NOTIFICATIONS_TEXT);
-        }
-    }
-
-    private Message addUserView() {
-        try {
-            DesignDocument userDocument = prepareDocument("_design/users");
-            Map<String, MapReduce> views = new HashMap<>();
-
-            MapReduce passwordFunction = new MapReduce();
-            passwordFunction.setMap("function(doc) { if(doc.email&& doc.password) { emit(doc.email, doc.password); } }");
-
-            MapReduce usernameFunction = new MapReduce();
-            usernameFunction.setMap("function(doc) { if(doc.email) { emit(doc.email, doc); } }");
-
-            MapReduce tokenFunction = new MapReduce();
-            tokenFunction.setMap("function(doc) { if (doc.userApiTokens) { doc.userApiTokens.forEach(function(token) { emit(token.hashedToken, doc.email); });}}");
+      userDocument.setViews(views);
+      Response resp = Utils.getCouchDbUserClient().design().synchronizeWithDb(userDocument);
 
-            views.put("password", passwordFunction);
-            views.put("username", usernameFunction);
-            views.put("token", tokenFunction);
-
-            userDocument.setViews(views);
-            Response resp = Utils.getCouchDbUserClient().design().synchronizeWithDb(userDocument);
-
-            if (resp.getError() != null) return Notifications.error(PREPARING_USERS_TEXT);
-            else return Notifications.success(PREPARING_USERS_TEXT);
-        } catch (Exception e) {
-            return Notifications.error(PREPARING_USERS_TEXT);
-        }
+      if (resp.getError() != null) {
+        logFailure(PREPARING_USERS_TEXT);
+      } else {
+        logSuccess(PREPARING_USERS_TEXT);
+      }
+    } catch (Exception e) {
+      logFailure(PREPARING_USERS_TEXT, e);
     }
+  }
 
-    private Message addLabelView() {
-        try {
-            DesignDocument labelDocument = prepareDocument("_design/categoryId");
-            Map<String, MapReduce> views = new HashMap<>();
+  private void addLabelView() {
+    try {
+      DesignDocument labelDocument = prepareDocument("_design/categoryId");
+      Map<String, MapReduce> views = new HashMap<>();
 
-            MapReduce categoryIdFunction = new MapReduce();
-            categoryIdFunction.setMap("function(doc) { if(doc.categoryId) { emit(doc.categoryId, doc); } }");
+      MapReduce categoryIdFunction = new MapReduce();
+      categoryIdFunction.setMap("function(doc) { if(doc.categoryId) { emit(doc.categoryId, doc); } }");
 
-            views.put("categoryId", categoryIdFunction);
+      views.put("categoryId", categoryIdFunction);
 
-            labelDocument.setViews(views);
-            Response resp = Utils.getCouchDbLabelClient().design().synchronizeWithDb(labelDocument);
+      labelDocument.setViews(views);
+      Response resp = Utils.getCouchDbLabelClient().design().synchronizeWithDb(labelDocument);
 
-            if (resp.getError() != null) return Notifications.error(PREPARING_USERS_TEXT);
-            else return Notifications.success(PREPARING_USERS_TEXT);
-        } catch (Exception e) {
-            return Notifications.error(PREPARING_USERS_TEXT);
-        }
+      if (resp.getError() != null) {
+        logFailure(PREPARING_USERS_TEXT);
+      } else {
+        logSuccess(PREPARING_USERS_TEXT);
+      }
+    } catch (Exception e) {
+      logFailure(PREPARING_USERS_TEXT, e);
     }
+  }
 
-    private Message addConnectionView() {
-        try {
-            DesignDocument connectionDocument = prepareDocument("_design/connection");
-            Map<String, MapReduce> views = new HashMap<>();
+  private void addConnectionView() {
+    try {
+      DesignDocument connectionDocument = prepareDocument("_design/connection");
+      Map<String, MapReduce> views = new HashMap<>();
 
-            MapReduce frequentFunction = new MapReduce();
-            frequentFunction.setMap("function(doc) { if(doc.from && doc.to) { emit([doc.from, doc.to] , 1 ); } }");
-            frequentFunction.setReduce("function (key, values) { return sum(values); }");
+      MapReduce frequentFunction = new MapReduce();
+      frequentFunction.setMap("function(doc) { if(doc.from && doc.to) { emit([doc.from, doc.to] , 1 ); } }");
+      frequentFunction.setReduce("function (key, values) { return sum(values); }");
 
-            views.put("frequent", frequentFunction);
+      views.put("frequent", frequentFunction);
 
-            connectionDocument.setViews(views);
-            Response resp = Utils.getCouchDbConnectionClient().design().synchronizeWithDb(connectionDocument);
-
-            if (resp.getError() != null) return Notifications.error("Preparing database 'connection'...");
-            else return Notifications.success("Preparing database 'connection'...");
-        } catch (Exception e) {
-            return Notifications.error("Preparing database 'connection'...");
-        }
-    }
+      connectionDocument.setViews(views);
+      Response resp = Utils.getCouchDbConnectionClient().design().synchronizeWithDb(connectionDocument);
 
-    private DesignDocument prepareDocument(String id) {
-        DesignDocument doc = new DesignDocument();
-        doc.setLanguage("javascript");
-        doc.setId(id);
-        return doc;
+      if (resp.getError() != null) {
+        logFailure("Preparing database 'connection'...");
+      } else {
+        logSuccess("Preparing database 'connection'...");
+      }
+    } catch (Exception e) {
+      logFailure("Preparing database 'connection'...", e);
     }
+  }
+
+  private DesignDocument prepareDocument(String id) {
+    DesignDocument doc = new DesignDocument();
+    doc.setLanguage("javascript");
+    doc.setId(id);
+    return doc;
+  }
 }
diff --git a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/setup/InstallationConfiguration.java b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/setup/InstallationConfiguration.java
index d6ad3e4..308d377 100644
--- a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/setup/InstallationConfiguration.java
+++ b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/setup/InstallationConfiguration.java
@@ -32,11 +32,15 @@ public class InstallationConfiguration {
 		List<InstallationStep> steps = new ArrayList<>();
 
 		steps.add(new CouchDbInstallationStep());
-		steps.add(new UserRegistrationInstallationStep(settings.getAdminEmail(), settings.getAdminPassword()));
+		steps.add(new UserRegistrationInstallationStep(
+						settings.getAdminUser(),
+						settings.getAdminPassword(),
+						settings.getInitialServiceAccountName(),
+						settings.getInitialServiceAccountSecret()));
 
 		if (settings.getInstallPipelineElements()) {
 			for(ExtensionsServiceEndpoint endpoint : new EndpointFetcher().getEndpoints()) {
-				steps.add(new PipelineElementInstallationStep(endpoint, settings.getAdminEmail()));
+				steps.add(new PipelineElementInstallationStep(endpoint, settings.getAdminUser()));
 			}
 		}
 		
diff --git a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/setup/InstallationStep.java b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/setup/InstallationStep.java
index 3f22826..fba1813 100644
--- a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/setup/InstallationStep.java
+++ b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/setup/InstallationStep.java
@@ -18,13 +18,36 @@
 
 package org.apache.streampipes.manager.setup;
 
-import org.apache.streampipes.model.message.Message;
 
-import java.util.List;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
-public interface InstallationStep {
+public abstract class InstallationStep {
 
-	List<Message> install();
+	public static final Logger LOG = LoggerFactory.getLogger(InstallationStep.class);
+
+	private int errorCount = 0;
+
+	public abstract void install();
+
+	public abstract String getTitle();
+
+	public void logSuccess(String info) {
+		LOG.info(info);
+	}
+
+	public void logFailure(String error) {
+		errorCount++;
+		LOG.error(error);
+	}
+
+	public void logFailure(String error, Exception e) {
+		errorCount++;
+		LOG.error(error, e);
+	}
+
+	public int getErrorCount() {
+		return errorCount;
+	}
 
-	String getTitle();
 }
diff --git a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/setup/Installer.java b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/setup/Installer.java
deleted file mode 100644
index 504bcfd..0000000
--- a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/setup/Installer.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package org.apache.streampipes.manager.setup;
-
-import org.apache.streampipes.config.backend.BackendConfig;
-import org.apache.streampipes.model.message.Message;
-import org.apache.streampipes.model.message.SetupStatusMessage;
-import org.apache.streampipes.model.client.setup.InitialSettings;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class Installer {
-
-	private List<InstallationStep> installationSteps;
-
-	public Installer(InitialSettings settings) {
-		this.installationSteps = InstallationConfiguration.getInstallationSteps(settings);
-	}
-	
-	public SetupStatusMessage install(Integer currentInstallationStepIndex) {
-		String nextInstallationStepTitle = "";
-		InstallationStep currentInstallationStep = installationSteps.get(currentInstallationStepIndex);
-		List<Message> result = new ArrayList<>();
-		result.addAll(currentInstallationStep.install());
-
-		if (currentInstallationStepIndex == (this.installationSteps.size() - 1)) {
-			BackendConfig.INSTANCE.setIsConfigured(true);
-		} else {
-			nextInstallationStepTitle = installationSteps.get(currentInstallationStepIndex +1).getTitle();
-		}
-
-		return new SetupStatusMessage(currentInstallationStepIndex, installationSteps.size(), result, nextInstallationStepTitle);
-	}
-	
-}
diff --git a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/setup/PipelineElementInstallationStep.java b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/setup/PipelineElementInstallationStep.java
index 109bc69..67fa355 100644
--- a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/setup/PipelineElementInstallationStep.java
+++ b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/setup/PipelineElementInstallationStep.java
@@ -22,39 +22,36 @@ import org.apache.streampipes.manager.operations.Operations;
 import org.apache.streampipes.model.client.endpoint.ExtensionsServiceEndpoint;
 import org.apache.streampipes.model.client.endpoint.ExtensionsServiceEndpointItem;
 import org.apache.streampipes.model.message.Message;
-import org.apache.streampipes.model.message.Notifications;
 
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 
-public class PipelineElementInstallationStep implements InstallationStep {
+public class PipelineElementInstallationStep extends InstallationStep {
 
   private ExtensionsServiceEndpoint endpoint;
-  private String userEmail;
+  private String principalName;
 
-  public PipelineElementInstallationStep(ExtensionsServiceEndpoint endpoint, String userEmail) {
+  public PipelineElementInstallationStep(ExtensionsServiceEndpoint endpoint, String principalName) {
     this.endpoint = endpoint;
-    this.userEmail = userEmail;
+    this.principalName = principalName;
   }
 
   @Override
-  public List<Message> install() {
+  public void install() {
     List<Message> statusMessages = new ArrayList<>();
     List<ExtensionsServiceEndpointItem> items = Operations.getEndpointUriContents(Collections.singletonList(endpoint));
     for(ExtensionsServiceEndpointItem item : items) {
       statusMessages.add(new EndpointItemParser().parseAndAddEndpointItem(item.getUri(),
-              userEmail, true, false));
+              principalName, true, false));
     }
 
-    Message installMessage;
     if (statusMessages.stream().allMatch(Message::isSuccess)) {
-      installMessage = Notifications.success(getTitle());
+      logSuccess(getTitle());
     } else {
-      installMessage = Notifications.error(getTitle());
+      logFailure(getTitle());
     }
 
-    return Collections.singletonList(installMessage);
   }
 
   @Override
diff --git a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/setup/UserRegistrationInstallationStep.java b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/setup/UserRegistrationInstallationStep.java
index 85f85b6..90e70f5 100644
--- a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/setup/UserRegistrationInstallationStep.java
+++ b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/setup/UserRegistrationInstallationStep.java
@@ -18,47 +18,65 @@
 
 package org.apache.streampipes.manager.setup;
 
-import org.apache.streampipes.model.message.Message;
-import org.apache.streampipes.model.message.Notifications;
+import org.apache.streampipes.model.client.user.Principal;
 import org.apache.streampipes.model.client.user.Role;
-import org.apache.streampipes.model.client.user.User;
+import org.apache.streampipes.model.client.user.ServiceAccount;
+import org.apache.streampipes.model.client.user.UserAccount;
 import org.apache.streampipes.storage.management.StorageDispatcher;
 import org.apache.streampipes.user.management.util.PasswordUtil;
 
 import java.security.NoSuchAlgorithmException;
 import java.security.spec.InvalidKeySpecException;
-import java.util.Arrays;
 import java.util.HashSet;
-import java.util.List;
 import java.util.Set;
 
-public class UserRegistrationInstallationStep implements InstallationStep {
+public class UserRegistrationInstallationStep extends InstallationStep {
 
-	private String adminEmail;
-	private String adminPassword;
-	private Set<Role> roles;
+	private final String adminEmail;
+	private final String adminPassword;
+	private final String initialServiceAccountName;
+	private final String initialServiceAccountSecret;
+	private final Set<Role> roles;
 	
-	public UserRegistrationInstallationStep(String adminEmail, String adminPassword) {
+	public UserRegistrationInstallationStep(String adminEmail,
+																					String adminPassword,
+																					String initialServiceAccountName,
+																					String initialServiceAccountSecret) {
 		this.adminEmail = adminEmail;
 		this.adminPassword = adminPassword;
+		this.initialServiceAccountName = initialServiceAccountName;
+		this.initialServiceAccountSecret = initialServiceAccountSecret;
 		roles = new HashSet<>();
 		roles.add(Role.SYSTEM_ADMINISTRATOR);
 		roles.add(Role.USER_DEMO);
 	}
 
 	@Override
-	public List<Message> install() {
+	public void install() {
 
 		try {
-			String encryptedPassword = PasswordUtil.encryptPassword(adminPassword);
-			StorageDispatcher.INSTANCE.getNoSqlStore().getUserStorageAPI().storeUser(new User(adminEmail,
-							encryptedPassword, roles));
-			return Arrays.asList(Notifications.success(getTitle()));
+			addAdminUser();
+			addServiceUser();
+
+			logSuccess(getTitle());
 		} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
-			e.printStackTrace();
-			return Arrays.asList(Notifications.error("Could not encrypt password"));
+			logFailure("Could not encrypt password");
 		}
+	}
+
+	private void addAdminUser() throws NoSuchAlgorithmException, InvalidKeySpecException {
+		String encryptedPassword = PasswordUtil.encryptPassword(adminPassword);
+		UserAccount user = UserAccount.from(adminEmail, encryptedPassword, roles);
+		storePrincipal(user);
+	}
+
+	private void addServiceUser() {
+		ServiceAccount serviceAccount = ServiceAccount.from(initialServiceAccountName, initialServiceAccountSecret, roles);
+		storePrincipal(serviceAccount);
+	}
 
+	private void storePrincipal(Principal principal) {
+		StorageDispatcher.INSTANCE.getNoSqlStore().getUserStorageAPI().storeUser(principal);
 	}
 
 	@Override
diff --git a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/storage/UserManagementService.java b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/storage/UserManagementService.java
index 3ad9f16..26cbe88 100644
--- a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/storage/UserManagementService.java
+++ b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/storage/UserManagementService.java
@@ -20,7 +20,7 @@ package org.apache.streampipes.manager.storage;
 
 import org.apache.streampipes.model.client.user.RegistrationData;
 import org.apache.streampipes.model.client.user.Role;
-import org.apache.streampipes.model.client.user.User;
+import org.apache.streampipes.model.client.user.UserAccount;
 import org.apache.streampipes.storage.api.IUserStorage;
 import org.apache.streampipes.storage.management.StorageDispatcher;
 import org.apache.streampipes.user.management.util.PasswordUtil;
@@ -32,10 +32,10 @@ import java.util.Set;
 public class UserManagementService {
 
   public Boolean registerUser(RegistrationData data, Set<Role> roles) {
-    User user = new User(data.getEmail(), data.getPassword(), roles);
 
     try {
       String encryptedPassword = PasswordUtil.encryptPassword(data.getPassword());
+      UserAccount user = UserAccount.from(data.getEmail(), encryptedPassword, roles);
       user.setPassword(encryptedPassword);
       getUserStorage().storeUser(user);
     } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
@@ -45,9 +45,9 @@ public class UserManagementService {
     return true;
   }
 
-  public static void setHideTutorial(String email, boolean hideTutorial) {
+  public static void setHideTutorial(String principalName, boolean hideTutorial) {
     IUserStorage userService = getUserStorage();
-    User user = userService.getUser(email);
+    UserAccount user = userService.getUserAccount(principalName);
     user.setHideTutorial(hideTutorial);
     userService.updateUser(user);
   }
diff --git a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/storage/UserService.java b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/storage/UserService.java
index c388461..fbb51c2 100644
--- a/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/storage/UserService.java
+++ b/streampipes-pipeline-management/src/main/java/org/apache/streampipes/manager/storage/UserService.java
@@ -18,14 +18,15 @@
 
 package org.apache.streampipes.manager.storage;
 
-import org.lightcouch.CouchDbClient;
 import org.apache.streampipes.commons.exceptions.ElementNotFoundException;
+import org.apache.streampipes.model.client.user.Principal;
+import org.apache.streampipes.model.client.user.UserAccount;
 import org.apache.streampipes.model.pipeline.Pipeline;
-import org.apache.streampipes.model.client.user.User;
 import org.apache.streampipes.storage.api.INoSqlStorage;
 import org.apache.streampipes.storage.api.IUserStorage;
 import org.apache.streampipes.storage.couchdb.utils.Utils;
 import org.apache.streampipes.storage.management.StorageDispatcher;
+import org.lightcouch.CouchDbClient;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -77,7 +78,7 @@ public class UserService {
     if (!checkUser(username)) {
       return;
     }
-    User user = userStorage.getUser(username);
+    Principal user = getPrincipal(username);
     user.addOwnSource(elementId, publicElement);
     userStorage.updateUser(user);
   }
@@ -86,7 +87,7 @@ public class UserService {
     if (!checkUser(username)) {
       return;
     }
-    User user = userStorage.getUser(username);
+    Principal user = getPrincipal(username);
     user.addOwnAction(elementId, publicElement);
     userStorage.updateUser(user);
   }
@@ -95,14 +96,14 @@ public class UserService {
     if (!checkUser(username)) {
       return;
     }
-    User user = userStorage.getUser(username);
+    Principal user = getPrincipal(username);
     user.addOwnSepa(elementId, publicElement);
     userStorage.updateUser(user);
   }
 
   public void deleteOwnAction(String username, String actionId) {
     if (checkUser(username)) {
-      User user = userStorage.getUser(username);
+      Principal user = getPrincipal(username);
       user.getOwnActions().removeIf(a -> a.getElementId().equals(actionId));
       userStorage.updateUser(user);
       //TODO remove actions from other users
@@ -111,7 +112,7 @@ public class UserService {
 
   public void deleteOwnSource(String username, String sourceId) {
     if (checkUser(username)) {
-      User user = userStorage.getUser(username);
+      Principal user = getPrincipal(username);
       user.getOwnSources().removeIf(a -> a.getElementId().equals(sourceId));
       userStorage.updateUser(user);
     }
@@ -119,7 +120,7 @@ public class UserService {
 
   public void deleteOwnSepa(String username, String sepaId) {
     if (checkUser(username)) {
-      User user = userStorage.getUser(username);
+      Principal user = getPrincipal(username);
       user.getOwnSepas().removeIf(a -> a.getElementId().equals(sepaId));
       userStorage.updateUser(user);
     }
@@ -133,8 +134,8 @@ public class UserService {
     if (!checkUser(username)) {
       return;
     }
-    User user = userStorage.getUser(username);
-    user.addPreferredSepa(elementId);
+    UserAccount user = getUserAccount(username);
+    user.addPreferredDataProcessor(elementId);
     userStorage.updateUser(user);
   }
 
@@ -142,8 +143,8 @@ public class UserService {
     if (!checkUser(username)) {
       return;
     }
-    User user = userStorage.getUser(username);
-    user.addPreferredAction(elementId);
+    UserAccount user = getUserAccount(username);
+    user.addPreferredDataSink(elementId);
     userStorage.updateUser(user);
   }
 
@@ -151,8 +152,8 @@ public class UserService {
     if (!checkUser(username)) {
       return;
     }
-    User user = userStorage.getUser(username);
-    user.addPreferredSource(elementId);
+    UserAccount user = getUserAccount(username);
+    user.addPreferredDataStream(elementId);
     userStorage.updateUser(user);
   }
 
@@ -161,8 +162,8 @@ public class UserService {
     if (!checkUser(username)) {
       return;
     }
-    User user = userStorage.getUser(username);
-    user.removePreferredSepa(elementId);
+    UserAccount user = getUserAccount(username);
+    user.removePreferredDataProcessor(elementId);
     dbClient.update(user);
     dbClient.shutdown();
   }
@@ -172,8 +173,8 @@ public class UserService {
     if (!checkUser(username)) {
       return;
     }
-    User user = userStorage.getUser(username);
-    user.removePreferredAction(elementId);
+    UserAccount user = getUserAccount(username);
+    user.removePreferredDataSink(elementId);
     dbClient.update(user);
     dbClient.shutdown();
   }
@@ -183,8 +184,8 @@ public class UserService {
     if (!checkUser(username)) {
       return;
     }
-    User user = userStorage.getUser(username);
-    user.removePreferredSource(elementId);
+    UserAccount user = getUserAccount(username);
+    user.removePreferredDataStream(elementId);
     dbClient.update(user);
     dbClient.shutdown();
   }
@@ -198,35 +199,35 @@ public class UserService {
   }
 
   public List<String> getFavoriteActionUris(String username) {
-    return userStorage.getUser(username).getPreferredActions();
+    return getUserAccount(username).getPreferredDataSinks();
   }
 
-  public List<String> getAvailableActionUris(String email) {
-    List<String> actions = new ArrayList<>(getOwnActionUris(email));
+  public List<String> getAvailableActionUris(String principalName) {
+    List<String> actions = new ArrayList<>(getOwnActionUris(principalName));
     userStorage
             .getAllUsers()
             .stream()
-            .filter(u -> !(u.getEmail().equals(email)))
+            .filter(u -> !(u.getPrincipalName().equals(principalName)))
             .map(u -> u.getOwnActions().stream().filter(p -> p.isPublicElement()).map(p -> p.getElementId()).collect(Collectors.toList())).forEach(actions::addAll);
     return actions;
   }
 
-  public List<String> getOwnSepaUris(String email) {
-    return userStorage.getUser(email).getOwnSepas().stream().map(r -> r.getElementId()).collect(Collectors.toList());
+  public List<String> getOwnSepaUris(String username) {
+    return userStorage.getUser(username).getOwnSepas().stream().map(r -> r.getElementId()).collect(Collectors.toList());
   }
 
-  public List<String> getAvailableSepaUris(String email) {
-    List<String> sepas = new ArrayList<>(getOwnSepaUris(email));
+  public List<String> getAvailableSepaUris(String principalName) {
+    List<String> sepas = new ArrayList<>(getOwnSepaUris(principalName));
     userStorage
             .getAllUsers()
             .stream()
-            .filter(u -> !(u.getEmail().equals(email)))
+            .filter(u -> !(u.getPrincipalName().equals(principalName)))
             .map(u -> u.getOwnSepas().stream().filter(p -> p.isPublicElement()).map(p -> p.getElementId()).collect(Collectors.toList())).forEach(sepas::addAll);
     return sepas;
   }
 
-  public List<String> getFavoriteSepaUris(String email) {
-    return userStorage.getUser(email).getPreferredSepas();
+  public List<String> getFavoriteSepaUris(String principalName) {
+    return getUserAccount(principalName).getPreferredDataProcessors();
   }
 
   public List<String> getOwnSourceUris(String email) {
@@ -238,12 +239,12 @@ public class UserService {
             .collect(Collectors.toList());
   }
 
-  public List<String> getAvailableSourceUris(String email) {
-    List<String> sources = new ArrayList<>(getOwnSepaUris(email));
+  public List<String> getAvailableSourceUris(String principalName) {
+    List<String> sources = new ArrayList<>(getOwnSepaUris(principalName));
     userStorage
             .getAllUsers()
             .stream()
-            .filter(u -> !(u.getEmail().equals(email)))
+            .filter(u -> !(u.getPrincipalName().equals(principalName)))
             .map(u -> u.getOwnSources()
                     .stream()
                     .filter(p -> p.isPublicElement())
@@ -254,7 +255,15 @@ public class UserService {
   }
 
   public List<String> getFavoriteSourceUris(String username) {
-    return userStorage.getUser(username).getPreferredSources();
+    return getUserAccount(username).getPreferredDataStreams();
+  }
+
+  public UserAccount getUserAccount(String principalName) {
+    return (UserAccount) getPrincipal(principalName);
+  }
+
+  private Principal getPrincipal(String principalName) {
+    return userStorage.getUser(principalName);
   }
 
   private IUserStorage userStorage() {
diff --git a/streampipes-rest/src/main/java/org/apache/streampipes/rest/filter/TokenAuthenticationFilter.java b/streampipes-rest/src/main/java/org/apache/streampipes/rest/filter/TokenAuthenticationFilter.java
index 04a3692..d9dc16d 100644
--- a/streampipes-rest/src/main/java/org/apache/streampipes/rest/filter/TokenAuthenticationFilter.java
+++ b/streampipes-rest/src/main/java/org/apache/streampipes/rest/filter/TokenAuthenticationFilter.java
@@ -3,7 +3,6 @@ package org.apache.streampipes.rest.filter;
 import org.apache.streampipes.storage.api.IUserStorage;
 import org.apache.streampipes.storage.management.StorageDispatcher;
 import org.apache.streampipes.user.management.jwt.JwtTokenProvider;
-import org.apache.streampipes.user.management.model.LocalUser;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
@@ -43,7 +42,7 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter {
 			if (StringUtils.hasText(jwt) && tokenProvider.validateJwtToken(jwt)) {
 				String username = tokenProvider.getUserIdFromToken(jwt);
 
-				UserDetails userDetails = new LocalUser(userStorage.getUser(username));
+				UserDetails userDetails = userStorage.getUser(username);
 				UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
 				authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
 
diff --git a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/Authentication.java b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/Authentication.java
index 433f0c2..fdd0824 100644
--- a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/Authentication.java
+++ b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/Authentication.java
@@ -28,7 +28,6 @@ import org.apache.streampipes.rest.core.base.impl.AbstractRestResource;
 import org.apache.streampipes.rest.shared.annotation.GsonWithIds;
 import org.apache.streampipes.rest.shared.annotation.JacksonSerialized;
 import org.apache.streampipes.user.management.jwt.JwtTokenProvider;
-import org.apache.streampipes.user.management.model.LocalUser;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.authentication.AuthenticationManager;
 import org.springframework.security.authentication.BadCredentialsException;
@@ -52,7 +51,7 @@ public class Authentication extends AbstractRestResource {
   @JacksonSerialized
   @POST
   @Path("/login")
-  public Response doLogin(ShiroAuthenticationRequest token) {
+  public Response doLogin(LoginRequest token) {
     try {
       org.springframework.security.core.Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(token.getUsername(), token.getPassword()));
       SecurityContextHolder.getContext().setAuthentication(authentication);
@@ -97,11 +96,11 @@ public class Authentication extends AbstractRestResource {
 
   private JwtAuthenticationResponse makeJwtResponse(org.springframework.security.core.Authentication auth) {
     String jwt = new JwtTokenProvider().createToken(auth);
-    LocalUser localUser = (LocalUser) auth.getPrincipal();
+    UserAccount localUser = (UserAccount) auth.getPrincipal();
     return JwtAuthenticationResponse.from(jwt, toUserInfo(localUser));
   }
 
-  private UserInfo toUserInfo(LocalUser localUser) {
+  private UserInfo toUserInfo(UserAccount localUser) {
     UserInfo userInfo = new UserInfo();
     userInfo.setUserId("id");
     userInfo.setEmail(localUser.getEmail());
diff --git a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/Setup.java b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/Setup.java
index 8f028b6..5b89102 100644
--- a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/Setup.java
+++ b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/Setup.java
@@ -20,16 +20,15 @@ package org.apache.streampipes.rest.impl;
 
 import com.google.gson.JsonObject;
 import org.apache.streampipes.config.backend.BackendConfig;
-import org.apache.streampipes.manager.setup.Installer;
 import org.apache.streampipes.model.client.setup.InitialSettings;
 import org.apache.streampipes.model.message.Notifications;
-import org.apache.streampipes.model.message.SetupStatusMessage;
 import org.apache.streampipes.rest.core.base.impl.AbstractRestResource;
-import org.apache.streampipes.rest.notifications.NotificationListener;
 import org.apache.streampipes.rest.shared.annotation.GsonWithIds;
-import org.apache.streampipes.rest.shared.annotation.JacksonSerialized;
 
-import javax.ws.rs.*;
+import javax.ws.rs.GET;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
 
@@ -46,23 +45,11 @@ public class Setup extends AbstractRestResource {
       return ok(obj.toString());
     } else {
       obj.addProperty("configured", false);
+      obj.addProperty("setupRunning", BackendConfig.INSTANCE.isConfigured());
       return ok(obj.toString());
     }
   }
 
-  @POST
-  @Path("/install/{currentInstallationStep}")
-  @JacksonSerialized
-  @Consumes(MediaType.APPLICATION_JSON)
-  @Produces(MediaType.APPLICATION_JSON)
-  public Response configure(InitialSettings settings, @PathParam("currentInstallationStep") Integer currentInstallationStep) {
-    SetupStatusMessage message = new Installer(settings).install(currentInstallationStep);
-    if (currentInstallationStep == (message.getInstallationStepCount() - 1)) {
-      new NotificationListener().contextInitialized(null);
-    }
-    return ok(message);
-  }
-
   @PUT
   @Path("/configuration")
   @GsonWithIds
diff --git a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/UserProfile.java b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/UserProfile.java
index 5a67aca..cb50c1d 100644
--- a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/UserProfile.java
+++ b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/UserProfile.java
@@ -19,6 +19,7 @@
 package org.apache.streampipes.rest.impl;
 
 import org.apache.streampipes.model.client.user.RawUserApiToken;
+import org.apache.streampipes.model.client.user.UserAccount;
 import org.apache.streampipes.model.message.Notifications;
 import org.apache.streampipes.rest.core.base.impl.AbstractAuthGuardedRestResource;
 import org.apache.streampipes.rest.shared.annotation.JacksonSerialized;
@@ -35,7 +36,7 @@ public class UserProfile extends AbstractAuthGuardedRestResource {
     @GET
     @Produces(MediaType.APPLICATION_JSON)
     public Response getUserDetails() {
-        org.apache.streampipes.model.client.user.User user = getUser(getAuthenticatedUsername());
+        UserAccount user = getUser(getAuthenticatedUsername());
         user.setPassword("");
 
         if (user != null) {
@@ -51,7 +52,7 @@ public class UserProfile extends AbstractAuthGuardedRestResource {
     public Response updateAppearanceMode(@PathParam("darkMode") boolean darkMode) {
         String authenticatedUserId = getAuthenticatedUsername();
         if (authenticatedUserId != null) {
-            org.apache.streampipes.model.client.user.User user = getUser(authenticatedUserId);
+            UserAccount user = getUser(authenticatedUserId);
             user.setDarkMode(darkMode);
             getUserStorage().updateUser(user);
 
@@ -64,10 +65,10 @@ public class UserProfile extends AbstractAuthGuardedRestResource {
     @PUT
     @Consumes(MediaType.APPLICATION_JSON)
     @Produces(MediaType.APPLICATION_JSON)
-    public Response updateUserDetails(org.apache.streampipes.model.client.user.User user) {
+    public Response updateUserDetails(UserAccount user) {
         String authenticatedUserId = getAuthenticatedUsername();
         if (user != null && authenticatedUserId.equals(user.getEmail())) {
-            org.apache.streampipes.model.client.user.User existingUser = getUser(user.getEmail());
+            UserAccount existingUser = getUser(user.getEmail());
             user.setPassword(existingUser.getPassword());
             user.setUserApiTokens(existingUser
                     .getUserApiTokens()
@@ -86,8 +87,8 @@ public class UserProfile extends AbstractAuthGuardedRestResource {
         }
     }
 
-    private org.apache.streampipes.model.client.user.User getUser(String email) {
-        return getUserStorage().getUser(email);
+    private UserAccount getUser(String email) {
+        return getUserStorage().getUserAccount(email);
     }
 
     @POST
diff --git a/streampipes-security-jwt/pom.xml b/streampipes-security-jwt/pom.xml
new file mode 100644
index 0000000..78f8d87
--- /dev/null
+++ b/streampipes-security-jwt/pom.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>streampipes-parent</artifactId>
+        <groupId>org.apache.streampipes</groupId>
+        <version>0.69.0-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>streampipes-security-jwt</artifactId>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.streampipes</groupId>
+            <artifactId>streampipes-commons</artifactId>
+            <version>0.69.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>io.jsonwebtoken</groupId>
+            <artifactId>jjwt-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.jsonwebtoken</groupId>
+            <artifactId>jjwt-impl</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.jsonwebtoken</groupId>
+            <artifactId>jjwt-jackson</artifactId>
+        </dependency>
+    </dependencies>
+
+</project>
diff --git a/streampipes-security-jwt/src/main/java/org/apache/streampipes/security/jwt/JwtTokenUtils.java b/streampipes-security-jwt/src/main/java/org/apache/streampipes/security/jwt/JwtTokenUtils.java
new file mode 100644
index 0000000..81035d4
--- /dev/null
+++ b/streampipes-security-jwt/src/main/java/org/apache/streampipes/security/jwt/JwtTokenUtils.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.streampipes.security.jwt;
+
+import io.jsonwebtoken.*;
+import io.jsonwebtoken.security.Keys;
+import io.jsonwebtoken.security.WeakKeyException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.crypto.SecretKey;
+import java.nio.charset.StandardCharsets;
+import java.util.Date;
+
+public class JwtTokenUtils {
+
+  private static final Logger LOG = LoggerFactory.getLogger(JwtTokenUtils.class);
+
+  public static String makeJwtToken(String subject,
+                                    String tokenSecret,
+                                    Date expirationDate) {
+
+    SecretKey key = Keys.hmacShaKeyFor(tokenSecret.getBytes(StandardCharsets.UTF_8));
+
+    return Jwts
+            .builder()
+            .setSubject(subject)
+            .setIssuedAt(new Date())
+            .setExpiration(expirationDate)
+            .signWith(key).compact();
+  }
+
+  public static String getUserIdFromToken(String tokenSecret,
+                                          String token) {
+    Claims claims = jwtParser(tokenSecret).parseClaimsJws(token).getBody();
+    return claims.getSubject();
+  }
+
+  private static JwtParser jwtParser(String tokenSecret) {
+    return Jwts.parserBuilder()
+            .setSigningKey(tokenSecret.getBytes(StandardCharsets.UTF_8))
+            .build();
+  }
+
+  public static boolean validateJwtToken(String tokenSecret,
+                                         String jwtToken) {
+    try {
+      jwtParser(tokenSecret).parseClaimsJws(jwtToken);
+      return true;
+    } catch (MalformedJwtException ex) {
+      LOG.error("Invalid JWT token");
+    } catch (ExpiredJwtException ex) {
+      LOG.error("Expired JWT token");
+    } catch (UnsupportedJwtException ex) {
+      LOG.error("Unsupported JWT token");
+    } catch (IllegalArgumentException ex) {
+      LOG.error("JWT claims are empty.");
+    } catch (WeakKeyException ex) {
+      LOG.error("Weak Key");
+    }
+    return false;
+  }
+}
diff --git a/streampipes-serializers-json/src/main/java/org/apache/streampipes/serializers/json/GsonSerializer.java b/streampipes-serializers-json/src/main/java/org/apache/streampipes/serializers/json/GsonSerializer.java
index bde286b..081a300 100644
--- a/streampipes-serializers-json/src/main/java/org/apache/streampipes/serializers/json/GsonSerializer.java
+++ b/streampipes-serializers-json/src/main/java/org/apache/streampipes/serializers/json/GsonSerializer.java
@@ -23,18 +23,19 @@ import com.google.gson.FieldAttributes;
 import com.google.gson.Gson;
 import com.google.gson.GsonBuilder;
 import org.apache.streampipes.model.*;
-import org.apache.streampipes.model.connect.rules.value.*;
-import org.apache.streampipes.model.message.Message;
+import org.apache.streampipes.model.client.user.Principal;
 import org.apache.streampipes.model.connect.adapter.*;
+import org.apache.streampipes.model.connect.rules.TransformationRuleDescription;
 import org.apache.streampipes.model.connect.rules.schema.CreateNestedRuleDescription;
 import org.apache.streampipes.model.connect.rules.schema.DeleteRuleDescription;
 import org.apache.streampipes.model.connect.rules.schema.MoveRuleDescription;
 import org.apache.streampipes.model.connect.rules.schema.RenameRuleDescription;
 import org.apache.streampipes.model.connect.rules.stream.EventRateTransformationRuleDescription;
 import org.apache.streampipes.model.connect.rules.stream.RemoveDuplicatesTransformationRuleDescription;
-import org.apache.streampipes.model.connect.rules.TransformationRuleDescription;
+import org.apache.streampipes.model.connect.rules.value.*;
 import org.apache.streampipes.model.grounding.TopicDefinition;
 import org.apache.streampipes.model.grounding.TransportProtocol;
+import org.apache.streampipes.model.message.Message;
 import org.apache.streampipes.model.output.OutputStrategy;
 import org.apache.streampipes.model.quality.EventPropertyQualityDefinition;
 import org.apache.streampipes.model.quality.EventStreamQualityDefinition;
@@ -57,6 +58,13 @@ public class GsonSerializer {
     return builder;
   }
 
+  public static GsonBuilder getPrincipalGsonBuilder() {
+    GsonBuilder builder = getGsonBuilder();
+    builder.registerTypeHierarchyAdapter(Principal.class, new PrincipalDeserializer());
+
+    return builder;
+  }
+
   public static Gson getAdapterGson() {
     return getAdapterGsonBuilder().create();
   }
diff --git a/streampipes-serializers-json/src/main/java/org/apache/streampipes/serializers/json/PrincipalDeserializer.java b/streampipes-serializers-json/src/main/java/org/apache/streampipes/serializers/json/PrincipalDeserializer.java
new file mode 100644
index 0000000..83cf3c9
--- /dev/null
+++ b/streampipes-serializers-json/src/main/java/org/apache/streampipes/serializers/json/PrincipalDeserializer.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+package org.apache.streampipes.serializers.json;
+
+import com.google.gson.*;
+import org.apache.streampipes.model.client.user.Principal;
+
+import java.lang.reflect.MalformedParameterizedTypeException;
+import java.lang.reflect.Type;
+
+public class PrincipalDeserializer implements JsonDeserializer<Principal>, JsonSerializer<Principal> {
+  @Override
+  public Principal deserialize(JsonElement json, Type typeInfo, JsonDeserializationContext context) throws JsonParseException {
+    JsonObject jsonObject = json.getAsJsonObject();
+    String type = jsonObject.get("field_type").getAsString();
+    JsonElement element = jsonObject.get("properties");
+    JsonObject tmp = element.getAsJsonObject();
+    if (jsonObject.has("_id")) {
+      tmp.addProperty("_id", jsonObject.get("_id").getAsString());
+    }
+    if (jsonObject.has("_rev")) {
+      tmp.addProperty("_rev", jsonObject.get("_rev").getAsString());
+    }
+
+    try {
+      return (Principal) GsonSerializer.getGson().fromJson(element, Class.forName(type));
+    } catch (ClassNotFoundException cnfe) {
+      throw new JsonParseException("Unknown element type: " + type, cnfe);
+    }
+  }
+
+  @Override
+  public JsonElement serialize(Principal src, Type type, JsonSerializationContext context) {
+    JsonObject result = new JsonObject();
+    try {
+      // Both types are required to deserialize adapters correctly
+      result.add("type", new JsonPrimitive(src.getClass().getCanonicalName()));
+      result.add("field_type", new JsonPrimitive(src.getClass().getCanonicalName()));
+      result.add("properties", GsonSerializer.getGson().toJsonTree(src));
+      if (src.getPrincipalId() != null) {
+        result.add("_id", new JsonPrimitive(src.getPrincipalId()));
+      }
+      if (src.getRev() != null) {
+        result.add("_rev", new JsonPrimitive(src.getRev()));
+      }
+    } catch (MalformedParameterizedTypeException e) {
+      e.printStackTrace();
+    }
+
+    return result;
+  }
+}
diff --git a/streampipes-storage-api/src/main/java/org/apache/streampipes/storage/api/IUserStorage.java b/streampipes-storage-api/src/main/java/org/apache/streampipes/storage/api/IUserStorage.java
index 25e4c84..4f1c7f8 100644
--- a/streampipes-storage-api/src/main/java/org/apache/streampipes/storage/api/IUserStorage.java
+++ b/streampipes-storage-api/src/main/java/org/apache/streampipes/storage/api/IUserStorage.java
@@ -17,18 +17,28 @@
  */
 package org.apache.streampipes.storage.api;
 
-import org.apache.streampipes.model.client.user.User;
+import org.apache.streampipes.model.client.user.Principal;
+import org.apache.streampipes.model.client.user.ServiceAccount;
+import org.apache.streampipes.model.client.user.UserAccount;
 
 import java.util.List;
 
 public interface IUserStorage {
-  List<User> getAllUsers();
+  List<Principal> getAllUsers();
 
-  User getUser(String email);
+  List<UserAccount> getAllUserAccounts();
 
-  void storeUser(User user);
+  List<ServiceAccount> getAllServiceAccounts();
 
-  void updateUser(User user);
+  Principal getUser(String principalName);
+
+  UserAccount getUserAccount(String principalName);
+
+  ServiceAccount getServiceAccount(String principalName);
+
+  void storeUser(Principal user);
+
+  void updateUser(Principal user);
 
   boolean emailExists(String email);
 
diff --git a/streampipes-storage-couchdb/src/main/java/org/apache/streampipes/storage/couchdb/impl/UserStorage.java b/streampipes-storage-couchdb/src/main/java/org/apache/streampipes/storage/couchdb/impl/UserStorage.java
index e560b6e..c4b6d7f 100644
--- a/streampipes-storage-couchdb/src/main/java/org/apache/streampipes/storage/couchdb/impl/UserStorage.java
+++ b/streampipes-storage-couchdb/src/main/java/org/apache/streampipes/storage/couchdb/impl/UserStorage.java
@@ -18,7 +18,9 @@
 
 package org.apache.streampipes.storage.couchdb.impl;
 
-import org.apache.streampipes.model.client.user.User;
+import org.apache.streampipes.model.client.user.Principal;
+import org.apache.streampipes.model.client.user.ServiceAccount;
+import org.apache.streampipes.model.client.user.UserAccount;
 import org.apache.streampipes.storage.api.IUserStorage;
 import org.apache.streampipes.storage.couchdb.dao.AbstractDao;
 import org.apache.streampipes.storage.couchdb.utils.Utils;
@@ -28,6 +30,7 @@ import org.slf4j.LoggerFactory;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.stream.Collectors;
 
 /**
  * User Storage.
@@ -35,26 +38,44 @@ import java.util.List;
  *
  *
  */
-public class UserStorage extends AbstractDao<User> implements IUserStorage {
+public class UserStorage extends AbstractDao<Principal> implements IUserStorage {
 
     Logger LOG = LoggerFactory.getLogger(UserStorage.class);
 
     public UserStorage() {
-        super(Utils::getCouchDbUserClient, User.class);
+        super(Utils::getCouchDbUserClient, Principal.class);
     }
 
     @Override
-    public List<User> getAllUsers()
+    public List<Principal> getAllUsers()
     {
-      List<User> users = findAll();
+      List<Principal> users = findAll();
     	return new ArrayList<>(users);
     }
 
     @Override
-    public User getUser(String email) {
+    public List<UserAccount> getAllUserAccounts() {
+        return findAll()
+                .stream()
+                .filter(u -> u instanceof UserAccount)
+                .map(u -> (UserAccount) u)
+                .collect(Collectors.toList());
+    }
+
+    @Override
+    public List<ServiceAccount> getAllServiceAccounts() {
+        return findAll()
+                .stream()
+                .filter(u -> u instanceof ServiceAccount)
+                .map(u -> (ServiceAccount) u)
+                .collect(Collectors.toList());
+    }
+
+    @Override
+    public Principal getUser(String principalName) {
         // TODO improve
         CouchDbClient couchDbClient = couchDbClientSupplier.get();
-        List<User> users = couchDbClient.view("users/username").key(email).includeDocs(true).query(User.class);
+        List<Principal> users = couchDbClient.view("users/username").key(principalName).includeDocs(true).query(Principal.class);
         if (users.size() != 1) {
             LOG.error("None or to many users with matching username");
         }
@@ -62,19 +83,29 @@ public class UserStorage extends AbstractDao<User> implements IUserStorage {
     }
 
     @Override
-    public void storeUser(User user) {
+    public UserAccount getUserAccount(String principalName) {
+        return (UserAccount) getUser(principalName);
+    }
+
+    @Override
+    public ServiceAccount getServiceAccount(String principalName) {
+        return (ServiceAccount) getUser(principalName);
+    }
+
+    @Override
+    public void storeUser(Principal user) {
         persist(user);
     }
 
     @Override
-    public void updateUser(User user) {
+    public void updateUser(Principal user) {
         update(user);
     }
 
     @Override
     public boolean emailExists(String email)
     {
-    	List<User> users = findAll();
+    	List<UserAccount> users = getAllUserAccounts();
     	return users
                 .stream()
                 .filter(u -> u.getEmail() != null)
@@ -88,12 +119,12 @@ public class UserStorage extends AbstractDao<User> implements IUserStorage {
     */
    @Override
    public boolean checkUser(String username) {
-       List<User> users = couchDbClientSupplier
+       List<Principal> users = couchDbClientSupplier
                .get()
                .view("users/username")
                .key(username)
                .includeDocs(true)
-               .query(User.class);
+               .query(Principal.class);
 
        return users.size() == 1;
    }
diff --git a/streampipes-storage-couchdb/src/main/java/org/apache/streampipes/storage/couchdb/utils/Utils.java b/streampipes-storage-couchdb/src/main/java/org/apache/streampipes/storage/couchdb/utils/Utils.java
index bbfd343..ba2aba5 100644
--- a/streampipes-storage-couchdb/src/main/java/org/apache/streampipes/storage/couchdb/utils/Utils.java
+++ b/streampipes-storage-couchdb/src/main/java/org/apache/streampipes/storage/couchdb/utils/Utils.java
@@ -18,9 +18,9 @@
 
 package org.apache.streampipes.storage.couchdb.utils;
 
+import org.apache.streampipes.serializers.json.GsonSerializer;
 import org.lightcouch.CouchDbClient;
 import org.lightcouch.CouchDbProperties;
-import org.apache.streampipes.serializers.json.GsonSerializer;
 
 public class Utils {
 
@@ -111,7 +111,7 @@ public class Utils {
   }
 
   public static CouchDbClient getCouchDbUserClient() {
-    return getCouchDbGsonClient("users");
+    return getCouchDbPrincipalClient("users");
   }
 
   public static CouchDbClient getCouchDbInternalUsersClient() {
@@ -152,6 +152,12 @@ public class Utils {
     return dbClient;
   }
 
+  private static CouchDbClient getCouchDbPrincipalClient(String dbname) {
+    CouchDbClient dbClient = new CouchDbClient(props(dbname));
+    dbClient.setGsonBuilder(GsonSerializer.getPrincipalGsonBuilder());
+    return dbClient;
+  }
+
   private static CouchDbClient getCouchDbAdapterClient(String dbname) {
     CouchDbClient dbClient = new CouchDbClient(props(dbname));
     dbClient.setGsonBuilder(GsonSerializer.getAdapterGsonBuilder());
diff --git a/streampipes-user-management/pom.xml b/streampipes-user-management/pom.xml
index 6e39373..779eb0f 100644
--- a/streampipes-user-management/pom.xml
+++ b/streampipes-user-management/pom.xml
@@ -41,6 +41,11 @@
         </dependency>
         <dependency>
             <groupId>org.apache.streampipes</groupId>
+            <artifactId>streampipes-security-jwt</artifactId>
+            <version>0.69.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.streampipes</groupId>
             <artifactId>streampipes-storage-management</artifactId>
             <version>0.69.0-SNAPSHOT</version>
         </dependency>
diff --git a/streampipes-user-management/src/main/java/org/apache/streampipes/user/management/jwt/JwtTokenProvider.java b/streampipes-user-management/src/main/java/org/apache/streampipes/user/management/jwt/JwtTokenProvider.java
index 1a90e14..773a63d 100644
--- a/streampipes-user-management/src/main/java/org/apache/streampipes/user/management/jwt/JwtTokenProvider.java
+++ b/streampipes-user-management/src/main/java/org/apache/streampipes/user/management/jwt/JwtTokenProvider.java
@@ -1,22 +1,15 @@
 package org.apache.streampipes.user.management.jwt;
 
-import io.jsonwebtoken.*;
-import io.jsonwebtoken.security.Keys;
-import io.jsonwebtoken.security.WeakKeyException;
 import org.apache.streampipes.config.backend.BackendConfig;
 import org.apache.streampipes.config.backend.model.LocalAuthConfig;
-import org.apache.streampipes.user.management.model.LocalUser;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import org.apache.streampipes.model.client.user.Principal;
+import org.apache.streampipes.security.jwt.JwtTokenUtils;
 import org.springframework.security.core.Authentication;
 
-import javax.crypto.SecretKey;
-import java.nio.charset.StandardCharsets;
 import java.util.Date;
 
 public class JwtTokenProvider {
 
-	private static final Logger LOG = LoggerFactory.getLogger(JwtTokenProvider.class);
 	private BackendConfig config;
 
 	public JwtTokenProvider() {
@@ -24,45 +17,18 @@ public class JwtTokenProvider {
 	}
 
 	public String createToken(Authentication authentication) {
-		LocalUser userPrincipal = (LocalUser) authentication.getPrincipal();
+		Principal userPrincipal = (Principal) authentication.getPrincipal();
 		Date tokenExpirationDate = makeExpirationDate();
-		SecretKey key = Keys.hmacShaKeyFor(tokenSecret().getBytes(StandardCharsets.UTF_8));
 
-		return Jwts
-						.builder()
-						.setSubject(userPrincipal.getEmail())
-						.setIssuedAt(new Date())
-						.setExpiration(tokenExpirationDate)
-				.signWith(key).compact();
+		return JwtTokenUtils.makeJwtToken(userPrincipal.getPrincipalName(), tokenSecret(), tokenExpirationDate);
 	}
 
 	public String getUserIdFromToken(String token) {
-		Claims claims = jwtParser().parseClaimsJws(token).getBody();
-		return claims.getSubject();
+		return JwtTokenUtils.getUserIdFromToken(tokenSecret(), token);
 	}
 
 	public boolean validateJwtToken(String jwtToken) {
-		try {
-			jwtParser().parseClaimsJws(jwtToken);
-			return true;
-		} catch (MalformedJwtException ex) {
-			LOG.error("Invalid JWT token");
-		} catch (ExpiredJwtException ex) {
-			LOG.error("Expired JWT token");
-		} catch (UnsupportedJwtException ex) {
-			LOG.error("Unsupported JWT token");
-		} catch (IllegalArgumentException ex) {
-			LOG.error("JWT claims are empty.");
-		} catch (WeakKeyException ex) {
-			LOG.error("Weak Key");
-		}
-		return false;
-	}
-
-	private JwtParser jwtParser() {
-		return Jwts.parserBuilder()
-						.setSigningKey(tokenSecret().getBytes(StandardCharsets.UTF_8))
-						.build();
+		return JwtTokenUtils.validateJwtToken(tokenSecret(), jwtToken);
 	}
 
 	private String tokenSecret() {
diff --git a/streampipes-user-management/src/main/java/org/apache/streampipes/user/management/model/LocalUser.java b/streampipes-user-management/src/main/java/org/apache/streampipes/user/management/model/LocalUser.java
deleted file mode 100644
index d7aeffd..0000000
--- a/streampipes-user-management/src/main/java/org/apache/streampipes/user/management/model/LocalUser.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-package org.apache.streampipes.user.management.model;
-
-import org.apache.streampipes.model.client.user.User;
-import org.springframework.security.core.GrantedAuthority;
-import org.springframework.security.core.userdetails.UserDetails;
-
-import java.util.Collection;
-import java.util.Collections;
-
-public class LocalUser extends User implements UserDetails {
-
-  public LocalUser(User user) {
-    this.username = user.getUsername();
-    this.email = user.getEmail();
-    this.ownSepas = user.getOwnSepas();
-    this.ownActions = user.getOwnActions();
-    this.darkMode = user.isDarkMode();
-    this.hideTutorial = user.isHideTutorial();
-    this.fullName = user.getFullName();
-    this.ownSources = user.getOwnSources();
-    this.preferredActions = user.getPreferredActions();
-    this.preferredSepas = user.getPreferredSepas();
-    this.preferredSources = user.getPreferredSources();
-    this.password = user.getPassword();
-    this.rev = user.getRev();
-    this.userId = user.getUserId();
-    this.roles= user.getRoles();
-    this.userApiTokens = user.getUserApiTokens();
-  }
-
-  @Override
-  public Collection<? extends GrantedAuthority> getAuthorities() {
-    return Collections.emptyList();
-  }
-
-  @Override
-  public String getPassword() {
-    return password;
-  }
-
-  @Override
-  public String getUsername() {
-    return email;
-  }
-
-  @Override
-  public boolean isAccountNonExpired() {
-    return true;
-  }
-
-  @Override
-  public boolean isAccountNonLocked() {
-    return true;
-  }
-
-  @Override
-  public boolean isCredentialsNonExpired() {
-    return true;
-  }
-
-  @Override
-  public boolean isEnabled() {
-    return true;
-  }
-}
diff --git a/streampipes-user-management/src/main/java/org/apache/streampipes/user/management/service/SpUserDetailsService.java b/streampipes-user-management/src/main/java/org/apache/streampipes/user/management/service/SpUserDetailsService.java
index dd6fff1..7bf27af 100644
--- a/streampipes-user-management/src/main/java/org/apache/streampipes/user/management/service/SpUserDetailsService.java
+++ b/streampipes-user-management/src/main/java/org/apache/streampipes/user/management/service/SpUserDetailsService.java
@@ -17,9 +17,7 @@
  */
 package org.apache.streampipes.user.management.service;
 
-import org.apache.streampipes.model.client.user.User;
 import org.apache.streampipes.storage.management.StorageDispatcher;
-import org.apache.streampipes.user.management.model.LocalUser;
 import org.springframework.security.core.userdetails.UserDetails;
 import org.springframework.security.core.userdetails.UserDetailsService;
 import org.springframework.security.core.userdetails.UsernameNotFoundException;
@@ -28,8 +26,6 @@ public class SpUserDetailsService implements UserDetailsService {
 
   @Override
   public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
-    User user = StorageDispatcher.INSTANCE.getNoSqlStore().getUserStorageAPI().getUser(s);
-    LocalUser localUser = new LocalUser(user);
-    return new LocalUser(localUser);
+    return StorageDispatcher.INSTANCE.getNoSqlStore().getUserStorageAPI().getUser(s);
   }
 }
diff --git a/streampipes-user-management/src/main/java/org/apache/streampipes/user/management/service/TokenService.java b/streampipes-user-management/src/main/java/org/apache/streampipes/user/management/service/TokenService.java
index a6eea99..d183c4c 100644
--- a/streampipes-user-management/src/main/java/org/apache/streampipes/user/management/service/TokenService.java
+++ b/streampipes-user-management/src/main/java/org/apache/streampipes/user/management/service/TokenService.java
@@ -17,29 +17,24 @@
  */
 package org.apache.streampipes.user.management.service;
 
-import com.google.gson.JsonObject;
 import org.apache.streampipes.model.client.user.RawUserApiToken;
-import org.apache.streampipes.model.client.user.User;
+import org.apache.streampipes.model.client.user.UserAccount;
 import org.apache.streampipes.storage.api.IUserStorage;
-import org.apache.streampipes.storage.couchdb.utils.Utils;
 import org.apache.streampipes.storage.management.StorageDispatcher;
 import org.apache.streampipes.user.management.util.TokenUtil;
-import org.lightcouch.CouchDbClient;
-
-import java.nio.file.attribute.UserPrincipalNotFoundException;
-import java.util.List;
 
 public class TokenService {
 
-  public RawUserApiToken createAndStoreNewToken(String email, RawUserApiToken baseToken) {
-    User user = getUserStorage().getUser(email);
+  public RawUserApiToken createAndStoreNewToken(String email,
+                                                RawUserApiToken baseToken) {
+    UserAccount user = getUserStorage().getUserAccount(email);
     RawUserApiToken generatedToken = TokenUtil.createToken(baseToken.getTokenName());
     storeToken(user, generatedToken);
     generatedToken.setHashedToken("");
     return generatedToken;
   }
 
-  private void storeToken(User user, RawUserApiToken generatedToken) {
+  private void storeToken(UserAccount user, RawUserApiToken generatedToken) {
     user.getUserApiTokens().add(TokenUtil.toUserToken(generatedToken));
     getUserStorage().updateUser(user);
   }
@@ -49,13 +44,4 @@ public class TokenService {
             .getNoSqlStore()
             .getUserStorageAPI();
   }
-
-  public User findUserForToken(String token) throws UserPrincipalNotFoundException {
-    CouchDbClient dbClient = Utils.getCouchDbUserClient();
-    List<JsonObject> users = dbClient.view("users/token").key(token).includeDocs(true).query(JsonObject.class);
-    if (users.size() != 1) {
-      throw new UserPrincipalNotFoundException("None or too many users with matching token");
-    }
-    return getUserStorage().getUser(users.get(0).get("email").getAsString());
-  }
 }
diff --git a/ui/src/app/core-model/gen/streampipes-model-client.ts b/ui/src/app/core-model/gen/streampipes-model-client.ts
index 98b0297..f6b10b4 100644
--- a/ui/src/app/core-model/gen/streampipes-model-client.ts
+++ b/ui/src/app/core-model/gen/streampipes-model-client.ts
@@ -16,11 +16,10 @@
  *
  */
 
-
 /* tslint:disable */
 /* eslint-disable */
 // @ts-nocheck
-// Generated using typescript-generator version 2.27.744 on 2021-09-30 14:45:03.
+// Generated using typescript-generator version 2.27.744 on 2021-10-01 13:23:19.
 
 export class Element {
     elementId: string;
@@ -99,6 +98,10 @@ export class FileMetadata {
     }
 }
 
+export interface GrantedAuthority {
+    authority: string;
+}
+
 export class MatchingResultMessage {
     description: string;
     matchingSuccessful: boolean;
@@ -122,6 +125,51 @@ export class MatchingResultMessage {
     }
 }
 
+export class Principal implements UserDetails {
+    accountEnabled: boolean;
+    accountExpired: boolean;
+    accountLocked: boolean;
+    accountNonExpired: boolean;
+    accountNonLocked: boolean;
+    authorities: GrantedAuthority[];
+    credentialsNonExpired: boolean;
+    enabled: boolean;
+    ownActions: Element[];
+    ownSepas: Element[];
+    ownSources: Element[];
+    password: string;
+    principalId: string;
+    principalName: string;
+    rev: string;
+    roles: Role[];
+    username: string;
+
+    static fromData(data: Principal, target?: Principal): Principal {
+        if (!data) {
+            return data;
+        }
+        const instance = target || new Principal();
+        instance.enabled = data.enabled;
+        instance.password = data.password;
+        instance.username = data.username;
+        instance.authorities = __getCopyArrayFn(__identity<GrantedAuthority>())(data.authorities);
+        instance.accountNonExpired = data.accountNonExpired;
+        instance.credentialsNonExpired = data.credentialsNonExpired;
+        instance.accountNonLocked = data.accountNonLocked;
+        instance.principalId = data.principalId;
+        instance.rev = data.rev;
+        instance.accountEnabled = data.accountEnabled;
+        instance.accountLocked = data.accountLocked;
+        instance.accountExpired = data.accountExpired;
+        instance.principalName = data.principalName;
+        instance.ownSources = __getCopyArrayFn(Element.fromData)(data.ownSources);
+        instance.ownSepas = __getCopyArrayFn(Element.fromData)(data.ownSepas);
+        instance.ownActions = __getCopyArrayFn(Element.fromData)(data.ownActions);
+        instance.roles = __getCopyArrayFn(__identity<Role>())(data.roles);
+        return instance;
+    }
+}
+
 export class RawUserApiToken {
     hashedToken: string;
     rawToken: string;
@@ -141,45 +189,30 @@ export class RawUserApiToken {
     }
 }
 
-export class User {
+export class UserAccount extends Principal {
     darkMode: boolean;
     email: string;
     fullName: string;
     hideTutorial: boolean;
-    ownActions: Element[];
-    ownSepas: Element[];
-    ownSources: Element[];
-    password: string;
-    preferredActions: string[];
-    preferredSepas: string[];
-    preferredSources: string[];
-    rev: string;
-    roles: Role[];
+    preferredDataProcessors: string[];
+    preferredDataSinks: string[];
+    preferredDataStreams: string[];
     userApiTokens: UserApiToken[];
-    userId: string;
-    username: string;
 
-    static fromData(data: User, target?: User): User {
+    static fromData(data: UserAccount, target?: UserAccount): UserAccount {
         if (!data) {
             return data;
         }
-        const instance = target || new User();
-        instance.userId = data.userId;
-        instance.rev = data.rev;
+        const instance = target || new UserAccount();
+        super.fromData(data, instance);
         instance.email = data.email;
-        instance.username = data.username;
         instance.fullName = data.fullName;
-        instance.password = data.password;
-        instance.ownSources = __getCopyArrayFn(Element.fromData)(data.ownSources);
-        instance.ownSepas = __getCopyArrayFn(Element.fromData)(data.ownSepas);
-        instance.ownActions = __getCopyArrayFn(Element.fromData)(data.ownActions);
-        instance.preferredSources = __getCopyArrayFn(__identity<string>())(data.preferredSources);
-        instance.preferredSepas = __getCopyArrayFn(__identity<string>())(data.preferredSepas);
-        instance.preferredActions = __getCopyArrayFn(__identity<string>())(data.preferredActions);
+        instance.preferredDataStreams = __getCopyArrayFn(__identity<string>())(data.preferredDataStreams);
+        instance.preferredDataProcessors = __getCopyArrayFn(__identity<string>())(data.preferredDataProcessors);
+        instance.preferredDataSinks = __getCopyArrayFn(__identity<string>())(data.preferredDataSinks);
         instance.userApiTokens = __getCopyArrayFn(UserApiToken.fromData)(data.userApiTokens);
         instance.hideTutorial = data.hideTutorial;
         instance.darkMode = data.darkMode;
-        instance.roles = __getCopyArrayFn(__identity<Role>())(data.roles);
         return instance;
     }
 }
@@ -199,6 +232,16 @@ export class UserApiToken {
     }
 }
 
+export interface UserDetails {
+    accountNonExpired: boolean;
+    accountNonLocked: boolean;
+    authorities: GrantedAuthority[];
+    credentialsNonExpired: boolean;
+    enabled: boolean;
+    password: string;
+    username: string;
+}
+
 export class UserInfo {
     darkMode: boolean;
     displayName: string;
diff --git a/ui/src/app/platform-services/apis/pipeline-template.service.ts b/ui/src/app/platform-services/apis/pipeline-template.service.ts
index 57fea33..1f9536b 100644
--- a/ui/src/app/platform-services/apis/pipeline-template.service.ts
+++ b/ui/src/app/platform-services/apis/pipeline-template.service.ts
@@ -18,7 +18,6 @@
 
 import { Injectable } from '@angular/core';
 import { HttpClient } from '@angular/common/http';
-import { AuthStatusService } from '../../services/auth-status.service';
 import {
   FreeTextStaticProperty,
   PipelineOperationStatus,
@@ -32,8 +31,7 @@ import { Observable } from 'rxjs';
 export class PipelineTemplateService {
 
   constructor(
-    private http: HttpClient,
-    private authStatusService: AuthStatusService
+    private http: HttpClient
   ) {
   }
 
diff --git a/ui/src/app/profile/components/basic-profile-settings.ts b/ui/src/app/profile/components/basic-profile-settings.ts
index 5e98d6b..cb9cc33 100644
--- a/ui/src/app/profile/components/basic-profile-settings.ts
+++ b/ui/src/app/profile/components/basic-profile-settings.ts
@@ -17,7 +17,7 @@
  */
 
 import { ProfileService } from '../profile.service';
-import { User } from '../../core-model/gen/streampipes-model-client';
+import { UserAccount } from '../../core-model/gen/streampipes-model-client';
 import { Directive } from '@angular/core';
 import { AppConstants } from '../../services/app.constants';
 import { JwtTokenStorageService } from '../../services/jwt-token-storage.service';
@@ -25,7 +25,7 @@ import { JwtTokenStorageService } from '../../services/jwt-token-storage.service
 @Directive()
 export abstract class BasicProfileSettings {
 
-  userData: User;
+  userData: UserAccount;
   profileLoaded = false;
   profileUpdating = false;
   errorMessage: string;
diff --git a/ui/src/app/profile/profile.module.ts b/ui/src/app/profile/profile.module.ts
index f6242d5..273bf9f 100644
--- a/ui/src/app/profile/profile.module.ts
+++ b/ui/src/app/profile/profile.module.ts
@@ -28,6 +28,7 @@ import {TokenManagementSettingsComponent} from "./components/token/token-managem
 import {GeneralProfileSettingsComponent} from "./components/general/general-profile-settings.component";
 import {ProfileService} from "./profile.service";
 import {MatDividerModule} from "@angular/material/divider";
+import { ClipboardModule } from '@angular/cdk/clipboard';
 
 @NgModule({
   imports: [
@@ -38,6 +39,7 @@ import {MatDividerModule} from "@angular/material/divider";
     MatButtonModule,
     CustomMaterialModule,
     CommonModule,
+    ClipboardModule,
   ],
   declarations: [
     GeneralProfileSettingsComponent,
diff --git a/ui/src/app/profile/profile.service.ts b/ui/src/app/profile/profile.service.ts
index d77ec18..262e89c 100644
--- a/ui/src/app/profile/profile.service.ts
+++ b/ui/src/app/profile/profile.service.ts
@@ -19,7 +19,7 @@
 import { Injectable } from '@angular/core';
 import { PlatformServicesCommons } from '../platform-services/apis/commons.service';
 import { HttpClient } from '@angular/common/http';
-import { RawUserApiToken, User } from '../core-model/gen/streampipes-model-client';
+import { RawUserApiToken, UserAccount } from '../core-model/gen/streampipes-model-client';
 import { Observable } from 'rxjs';
 import { map } from 'rxjs/operators';
 import { Message } from '../core-model/gen/streampipes-model';
@@ -32,13 +32,13 @@ export class ProfileService {
 
   }
 
-  getUserProfile(): Observable<User> {
+  getUserProfile(): Observable<UserAccount> {
     return this.http.get(this.profilePath).pipe(map(response => {
-      return User.fromData(response as any);
+      return UserAccount.fromData(response as any);
     }));
   }
 
-  updateUserProfile(userData: User): Observable<Message> {
+  updateUserProfile(userData: UserAccount): Observable<Message> {
     return this.http.put(this.profilePath, userData).pipe(map(response => {
       return Message.fromData(response as any);
     }));