diff --git a/docker-compose.yml b/docker-compose.yml
index df03d641e5990cd8b85c106b4ddd4ee0e8b16bea..5c03c4777defb8fbca4b7785258e9d94aa61f379 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -11,7 +11,7 @@ volumes:
 
 networks:
   public:
-    name: core
+    name: public
     driver: bridge
     ipam:
       config:
@@ -23,7 +23,7 @@ networks:
       config:
         - subnet: 172.28.0.0/16
   core:
-    name: public
+    name: core
     driver: bridge
     ipam:
       config:
@@ -361,6 +361,7 @@ services:
     image: fda-ui
     networks:
       core:
+      public:
     ports:
       - "3000:3000"
     env_file:
diff --git a/fda-authentication-service/rest-service/src/main/java/at/tuwien/endpoints/TokenEndpoint.java b/fda-authentication-service/rest-service/src/main/java/at/tuwien/endpoints/TokenEndpoint.java
index 0fed6c2a5aa62ac0140b23d4e4aca73549725555..d72d0d3eced4ee5881d3f105b4be35e92af3c22c 100644
--- a/fda-authentication-service/rest-service/src/main/java/at/tuwien/endpoints/TokenEndpoint.java
+++ b/fda-authentication-service/rest-service/src/main/java/at/tuwien/endpoints/TokenEndpoint.java
@@ -81,16 +81,17 @@ public class TokenEndpoint {
                 .body(dto);
     }
 
-    @DeleteMapping("/{hash}")
+    @DeleteMapping("/{id}")
     @Transactional
     @Timed(value = "token.delete", description = "Time needed to delete the developer tokens")
     @Operation(summary = "Delete developer token", security = @SecurityRequirement(name = "bearerAuth"))
-    public void delete(@NotNull @PathVariable("hash") String hash,
+    public void delete(@NotNull @PathVariable("id") Long id,
                        @NotNull Principal principal) throws TokenNotFoundException, UserNotFoundException {
-        log.debug("endpoint delete developer token, hash={}, principal={}", hash, principal);
-        final Token token = tokenService.findOne(hash);
+        log.debug("endpoint delete developer token, id={}, principal={}", id, principal);
+        final Token token = tokenService.findOne(id);
         log.trace("found token {}", token);
         tokenService.delete(token.getTokenHash(), principal);
+        log.info("Deleted token with id {}", id);
     }
 
 }
\ No newline at end of file
diff --git a/fda-authentication-service/rest-service/src/test/java/at/tuwien/BaseUnitTest.java b/fda-authentication-service/rest-service/src/test/java/at/tuwien/BaseUnitTest.java
index b228f91cb143ea25252ba1bb2e2dc248c633c0f1..e3749b24406f4121898982f388777574f753740d 100644
--- a/fda-authentication-service/rest-service/src/test/java/at/tuwien/BaseUnitTest.java
+++ b/fda-authentication-service/rest-service/src/test/java/at/tuwien/BaseUnitTest.java
@@ -1,13 +1,19 @@
 package at.tuwien;
 
-import at.tuwien.api.auth.SignupRequestDto;
+import at.tuwien.api.user.UserDetailsDto;
+import at.tuwien.entities.user.RoleType;
 import at.tuwien.entities.user.TimeSecret;
 import at.tuwien.entities.user.Token;
 import at.tuwien.entities.user.User;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
 import org.springframework.test.context.TestPropertySource;
 
+import java.security.Principal;
 import java.time.Instant;
 import java.time.temporal.ChronoUnit;
+import java.util.List;
 
 @TestPropertySource(locations = "classpath:application.properties")
 public abstract class BaseUnitTest {
@@ -38,9 +44,21 @@ public abstract class BaseUnitTest {
             .emailVerified(USER_1_VERIFIED)
             .themeDark(USER_1_THEME_DARK)
             .created(USER_1_CREATED)
+            .roles(List.of(RoleType.ROLE_RESEARCHER))
             .lastModified(USER_1_LAST_MODIFIED)
             .build();
 
+    public final static UserDetails USER_1_DETAILS = UserDetailsDto.builder()
+            .username(USER_1_USERNAME)
+            .email(USER_1_EMAIL)
+            .password(USER_1_PASSWORD)
+            .authorities(List.of(new SimpleGrantedAuthority("ROLE_RESEARCHER")))
+            .build();
+
+    public final static Principal USER_1_PRINCIPAL = new UsernamePasswordAuthenticationToken(USER_1_DETAILS,
+            USER_1_PASSWORD, USER_1_DETAILS.getAuthorities());
+
+
     public final static Long USER_2_ID = 2L;
     public final static String USER_2_EMAIL = "jane.doe@example.com";
     public final static String USER_2_USERNAME = "jdoe2";
@@ -59,37 +77,64 @@ public abstract class BaseUnitTest {
             .emailVerified(USER_2_VERIFIED)
             .themeDark(USER_2_THEME_DARK)
             .created(USER_2_CREATED)
+            .roles(List.of(RoleType.ROLE_RESEARCHER))
             .lastModified(USER_2_LAST_MODIFIED)
             .build();
 
-    public final static Long TOKEN_1_ID = 1L;
-    public final static Boolean TOKEN_1_PROCESSED = false;
-    public final static String TOKEN_1_TOKEN = "mysecrettokenrandomlygenerated";
-    public final static Instant TOKEN_1_VALID_TO = Instant.now()
+    public final static UserDetails USER_2_DETAILS = UserDetailsDto.builder()
+            .username(USER_2_USERNAME)
+            .email(USER_2_EMAIL)
+            .password(USER_2_PASSWORD)
+            .authorities(List.of(new SimpleGrantedAuthority("ROLE_RESEARCHER")))
+            .build();
+
+    public final static Principal USER_2_PRINCIPAL = new UsernamePasswordAuthenticationToken(USER_2_DETAILS,
+            USER_2_PASSWORD, USER_2_DETAILS.getAuthorities());
+
+    public final static Long TIME_SECRET_1_ID = 1L;
+    public final static Boolean TIME_SECRET_1_PROCESSED = false;
+    public final static String TIME_SECRET_1_TOKEN = "mysecrettokenrandomlygenerated";
+    public final static Instant TIME_SECRET_1_VALID_TO = Instant.now()
             .plus(1, ChronoUnit.DAYS);
 
-    public final static Long TOKEN_2_ID = 2L;
-    public final static Boolean TOKEN_2_PROCESSED = true;
-    public final static String TOKEN_2_TOKEN = "blahblahblah";
-    public final static Instant TOKEN_2_VALID_TO = Instant.now()
+    public final static Long TIME_SECRET_2_ID = 2L;
+    public final static Boolean TIME_SECRET_2_PROCESSED = true;
+    public final static String TIME_SECRET_2_TOKEN = "blahblahblah";
+    public final static Instant TIME_SECRET_2_VALID_TO = Instant.now()
             .plus(1, ChronoUnit.DAYS);
 
-    public final static TimeSecret TOKEN_1 = TimeSecret.builder()
-            .id(TOKEN_1_ID)
+    public final static TimeSecret TIME_SECRET_1 = TimeSecret.builder()
+            .id(TIME_SECRET_1_ID)
             .uid(USER_1_ID)
             .user(USER_1)
-            .token(TOKEN_1_TOKEN)
-            .processed(TOKEN_1_PROCESSED)
-            .validTo(TOKEN_1_VALID_TO)
+            .token(TIME_SECRET_1_TOKEN)
+            .processed(TIME_SECRET_1_PROCESSED)
+            .validTo(TIME_SECRET_1_VALID_TO)
             .build();
 
-    public final static TimeSecret TOKEN_2 = TimeSecret.builder()
-            .id(TOKEN_2_ID)
+    public final static TimeSecret TIME_SECRET_2 = TimeSecret.builder()
+            .id(TIME_SECRET_2_ID)
             .uid(USER_2_ID)
             .user(USER_2)
-            .token(TOKEN_2_TOKEN)
-            .processed(TOKEN_2_PROCESSED)
-            .validTo(TOKEN_2_VALID_TO)
+            .token(TIME_SECRET_2_TOKEN)
+            .processed(TIME_SECRET_2_PROCESSED)
+            .validTo(TIME_SECRET_2_VALID_TO)
+            .build();
+
+    public final static Long TOKEN_1_ID = 1L;
+    public final static Instant TOKEN_1_EXPIRES = Instant.now().plus(100000000, ChronoUnit.MILLIS);
+
+    public final static Token TOKEN_1 = Token.builder()
+            .id(TOKEN_1_ID)
+            .expires(TOKEN_1_EXPIRES)
+            .build();
+
+    public final static Long TOKEN_2_ID = 2L;
+    public final static Instant TOKEN_2_EXPIRES = Instant.now().plus(100000000, ChronoUnit.MILLIS);
+
+    public final static Token TOKEN_2 = Token.builder()
+            .id(TOKEN_2_ID)
+            .expires(TOKEN_2_EXPIRES)
             .build();
 
 }
diff --git a/fda-authentication-service/rest-service/src/test/java/at/tuwien/config/H2Utils.java b/fda-authentication-service/rest-service/src/test/java/at/tuwien/config/H2Utils.java
new file mode 100644
index 0000000000000000000000000000000000000000..dd93123161d36dbc189ed500e0a4d94c1d6b2fe8
--- /dev/null
+++ b/fda-authentication-service/rest-service/src/test/java/at/tuwien/config/H2Utils.java
@@ -0,0 +1,38 @@
+package at.tuwien.config;
+
+import lombok.SneakyThrows;
+import lombok.extern.log4j.Log4j2;
+import org.codehaus.plexus.util.FileUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.persistence.EntityManager;
+import java.io.File;
+import java.io.IOException;
+
+@Log4j2
+@Component
+public class H2Utils {
+
+    @Autowired
+    private EntityManager entityManager;
+
+    @Transactional
+    public void runQuery(String query) {
+        log.debug("query={}", query);
+        entityManager.createNativeQuery(query)
+                .executeUpdate();
+    }
+
+    @Transactional
+    public void runScript(String scriptName) {
+        try {
+            runQuery(FileUtils.fileRead(new File("./src/test/resources/" + scriptName)));
+        } catch (IOException e) {
+            log.error("Failed to load script {}", scriptName);
+            throw new RuntimeException("Failed to load script", e);
+        }
+    }
+
+}
diff --git a/fda-authentication-service/rest-service/src/test/java/at/tuwien/service/AuthenticationServiceIntegrationTest.java b/fda-authentication-service/rest-service/src/test/java/at/tuwien/service/AuthenticationServiceIntegrationTest.java
index e447b521ed9f2976d5d03d7faae512d7bb439b28..49608e0f1d2ab2a70c678c58042a9e4ba4d12dba 100644
--- a/fda-authentication-service/rest-service/src/test/java/at/tuwien/service/AuthenticationServiceIntegrationTest.java
+++ b/fda-authentication-service/rest-service/src/test/java/at/tuwien/service/AuthenticationServiceIntegrationTest.java
@@ -41,10 +41,10 @@ public class AuthenticationServiceIntegrationTest extends BaseUnitTest {
     public void beforeEach() {
         final User u1 = userRepository.save(USER_1);
         final User u2 = userRepository.save(USER_2);
-        TOKEN_1.setUser(u1);
-        tokenRepository.save(TOKEN_1);
-        TOKEN_2.setUser(u2);
-        tokenRepository.save(TOKEN_2);
+        TIME_SECRET_1.setUser(u1);
+        tokenRepository.save(TIME_SECRET_1);
+        TIME_SECRET_2.setUser(u2);
+        tokenRepository.save(TIME_SECRET_2);
     }
 
     @Test
diff --git a/fda-authentication-service/rest-service/src/test/java/at/tuwien/service/TokenServiceIntegrationTest.java b/fda-authentication-service/rest-service/src/test/java/at/tuwien/service/TimeSecretUnitTest.java
similarity index 80%
rename from fda-authentication-service/rest-service/src/test/java/at/tuwien/service/TokenServiceIntegrationTest.java
rename to fda-authentication-service/rest-service/src/test/java/at/tuwien/service/TimeSecretUnitTest.java
index 1a2002e9fac1cb1af9cceb0e00cc27ef36a41574..b7b8d672ec2588d03966f619d04224c058a54574 100644
--- a/fda-authentication-service/rest-service/src/test/java/at/tuwien/service/TokenServiceIntegrationTest.java
+++ b/fda-authentication-service/rest-service/src/test/java/at/tuwien/service/TimeSecretUnitTest.java
@@ -21,16 +21,16 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
 @DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD)
 @SpringBootTest
 @ExtendWith(SpringExtension.class)
-public class TokenServiceIntegrationTest extends BaseUnitTest {
+public class TimeSecretUnitTest extends BaseUnitTest {
 
     @MockBean
     private ReadyConfig readyConfig;
 
     @Autowired
-    private TimeSecretService tokenService;
+    private TimeSecretService timeSecretService;
 
     @Autowired
-    private TimeSecretRepository tokenRepository;
+    private TimeSecretRepository timeSecretRepository;
 
     @Autowired
     private UserRepository userRepository;
@@ -38,16 +38,16 @@ public class TokenServiceIntegrationTest extends BaseUnitTest {
     @BeforeEach
     public void beforeEach() {
         userRepository.save(USER_1);
-        tokenRepository.save(TOKEN_1);
+        timeSecretRepository.save(TIME_SECRET_1);
     }
 
     @Test
     public void updateVerification_succeeds() throws SecretInvalidException {
 
         /* test */
-        tokenService.invalidate(TOKEN_1_TOKEN);
+        timeSecretService.invalidate(TIME_SECRET_1_TOKEN);
         assertThrows(SecretInvalidException.class, () -> {
-            tokenService.invalidate(TOKEN_1_TOKEN);
+            timeSecretService.invalidate(TIME_SECRET_1_TOKEN);
         });
     }
 
diff --git a/fda-authentication-service/rest-service/src/test/java/at/tuwien/service/TokenIntegrationTest.java b/fda-authentication-service/rest-service/src/test/java/at/tuwien/service/TokenIntegrationTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..239515c03a53448926dfb741ddc3d410472ac4a3
--- /dev/null
+++ b/fda-authentication-service/rest-service/src/test/java/at/tuwien/service/TokenIntegrationTest.java
@@ -0,0 +1,110 @@
+package at.tuwien.service;
+
+import at.tuwien.BaseUnitTest;
+import at.tuwien.auth.JwtUtils;
+import at.tuwien.config.H2Utils;
+import at.tuwien.config.ReadyConfig;
+import at.tuwien.entities.user.Token;
+import at.tuwien.exception.UserNotFoundException;
+import at.tuwien.repositories.TokenRepository;
+import at.tuwien.repositories.UserRepository;
+import lombok.extern.log4j.Log4j2;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+import javax.servlet.ServletException;
+
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+@Log4j2
+@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD)
+@SpringBootTest
+@ExtendWith(SpringExtension.class)
+public class TokenIntegrationTest extends BaseUnitTest {
+
+    @MockBean
+    private ReadyConfig readyConfig;
+
+    @Autowired
+    private TokenService tokenService;
+
+    @Autowired
+    private UserRepository userRepository;
+
+    @Autowired
+    private TokenRepository tokenRepository;
+
+    @Autowired
+    private JwtUtils jwtUtils;
+
+    @Autowired
+    private H2Utils h2Utils;
+
+    @BeforeEach
+    public void beforeEach() {
+        userRepository.save(USER_1);
+        h2Utils.runScript("post-init.sql");
+    }
+
+    @Test
+    public void check_succeeds() throws ServletException {
+
+        /* mock */
+        final String jwt = jwtUtils.generateJwtToken(USER_1_USERNAME, TOKEN_1_EXPIRES);
+        final Token entity = Token.builder()
+                .token(jwt)
+                .tokenHash(JwtUtils.toHash(jwt))
+                .creator(USER_1_ID)
+                .expires(TOKEN_1_EXPIRES)
+                .build();
+        tokenRepository.save(entity) /* mock as invalid by the view script in ./resources/post-init.sql */;
+        final String jwt2 = jwtUtils.generateJwtToken(USER_1_USERNAME, TOKEN_2_EXPIRES);
+        final Token entity2 = Token.builder()
+                .token(jwt2)
+                .tokenHash(JwtUtils.toHash(jwt2))
+                .creator(USER_1_ID)
+                .expires(TOKEN_2_EXPIRES)
+                .build();
+        tokenRepository.save(entity2);
+
+        /* test */
+        tokenService.check(jwt2);
+    }
+
+    @Test
+    public void check_revoked_fails() {
+
+        /* mock */
+        final String jwt = jwtUtils.generateJwtToken(USER_1_USERNAME, TOKEN_1_EXPIRES);
+        final Token entity = Token.builder()
+                .token(jwt)
+                .tokenHash(JwtUtils.toHash(jwt))
+                .creator(USER_1_ID)
+                .expires(TOKEN_1_EXPIRES)
+                .build();
+        final Token token = tokenRepository.save(entity);
+        tokenRepository.deleteById(token.getId());
+
+        /* test */
+        assertThrows(ServletException.class, () -> {
+            tokenService.check(jwt);
+        });
+    }
+
+    @Test
+    public void create_userNotFound_fails() {
+
+        /* test */
+        assertThrows(UserNotFoundException.class, () -> {
+            tokenService.create(USER_2_PRINCIPAL);
+        });
+    }
+
+}
diff --git a/fda-authentication-service/rest-service/src/test/resources/application.properties b/fda-authentication-service/rest-service/src/test/resources/application.properties
index 2c7e1df4ae863589b65f0d3de2b589254cac612f..c1c50277573f1ae1d4527c6f8bcc5b635e6d1894 100644
--- a/fda-authentication-service/rest-service/src/test/resources/application.properties
+++ b/fda-authentication-service/rest-service/src/test/resources/application.properties
@@ -9,7 +9,7 @@ spring.cloud.config.discovery.enabled = false
 spring.cloud.config.enabled = false
 
 # disable datasource
-spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_ON_EXIT=FALSE;INIT=CREATE SCHEMA IF NOT EXISTS FDA
+spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_ON_EXIT=FALSE;INIT=runscript from './src/test/resources/pre-init.sql'
 spring.datasource.driverClassName=org.h2.Driver
 spring.datasource.username=sa
 spring.datasource.password=password
diff --git a/fda-authentication-service/rest-service/src/test/resources/post-init.sql b/fda-authentication-service/rest-service/src/test/resources/post-init.sql
new file mode 100644
index 0000000000000000000000000000000000000000..4e18b3e6e26aed4aac42a399f417d2cdf9afd5be
--- /dev/null
+++ b/fda-authentication-service/rest-service/src/test/resources/post-init.sql
@@ -0,0 +1,8 @@
+-- Modified for H2
+-- Assume id=1 is invalid
+-- Assume id=2 is still valid token
+CREATE VIEW IF NOT EXISTS mdb_valid_tokens AS
+(
+SELECT `id`, `token_hash`, `creator`, `created`, `expires`, `last_used`
+FROM (SELECT `id`, `token_hash`, `creator`, `created`, `expires`, `last_used` FROM FDA.`mdb_tokens`)
+WHERE `id` != 1 GROUP BY `id`);
\ No newline at end of file
diff --git a/fda-authentication-service/rest-service/src/test/resources/pre-init.sql b/fda-authentication-service/rest-service/src/test/resources/pre-init.sql
new file mode 100644
index 0000000000000000000000000000000000000000..173e6d5b1c05fe3788b0b5c964caed733e6c41f0
--- /dev/null
+++ b/fda-authentication-service/rest-service/src/test/resources/pre-init.sql
@@ -0,0 +1 @@
+CREATE SCHEMA IF NOT EXISTS FDA;
\ No newline at end of file
diff --git a/fda-authentication-service/services/src/main/java/at/tuwien/auth/AuthTokenFilter.java b/fda-authentication-service/services/src/main/java/at/tuwien/auth/AuthTokenFilter.java
index eb8d9548da45e84a3a25b9d55a759387bc9d8fdd..dac68f16eebc970088c4e42f1096f9a13cbb3316 100644
--- a/fda-authentication-service/services/src/main/java/at/tuwien/auth/AuthTokenFilter.java
+++ b/fda-authentication-service/services/src/main/java/at/tuwien/auth/AuthTokenFilter.java
@@ -1,5 +1,6 @@
 package at.tuwien.auth;
 
+import at.tuwien.service.TokenService;
 import at.tuwien.service.impl.UserDetailsServiceImpl;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
@@ -20,10 +21,12 @@ import java.io.IOException;
 public class AuthTokenFilter extends OncePerRequestFilter {
 
     private final JwtUtils jwtUtils;
+    private final TokenService tokenService;
     private final UserDetailsServiceImpl userDetailsService;
 
-    public AuthTokenFilter(JwtUtils jwtUtils, UserDetailsServiceImpl userDetailsService) {
+    public AuthTokenFilter(JwtUtils jwtUtils, TokenService tokenService, UserDetailsServiceImpl userDetailsService) {
         this.jwtUtils = jwtUtils;
+        this.tokenService = tokenService;
         this.userDetailsService = userDetailsService;
     }
 
@@ -32,6 +35,7 @@ public class AuthTokenFilter extends OncePerRequestFilter {
             throws ServletException, IOException {
         final String jwt = parseJwt(request);
         if (jwt != null && jwtUtils.validateJwtToken(jwt)) {
+            tokenService.check(jwt);
             final String username = jwtUtils.getUserNameFromJwtToken(jwt);
             final UserDetails userDetails;
             try {
diff --git a/fda-authentication-service/services/src/main/java/at/tuwien/auth/JwtUtils.java b/fda-authentication-service/services/src/main/java/at/tuwien/auth/JwtUtils.java
index 36fcb3b5e2d76be64aa9242cdcba77b638431124..12e93c0eb1632ba18ed9606747d534033c5baacf 100644
--- a/fda-authentication-service/services/src/main/java/at/tuwien/auth/JwtUtils.java
+++ b/fda-authentication-service/services/src/main/java/at/tuwien/auth/JwtUtils.java
@@ -5,6 +5,7 @@ import com.auth0.jwt.algorithms.Algorithm;
 import com.auth0.jwt.exceptions.JWTDecodeException;
 import com.auth0.jwt.interfaces.DecodedJWT;
 import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.codec.digest.DigestUtils;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.stereotype.Component;
 
@@ -40,6 +41,10 @@ public class JwtUtils {
                 .getSubject();
     }
 
+    public static String toHash(String token) {
+        return DigestUtils.sha256Hex(token);
+    }
+
     public boolean validateJwtToken(String authToken) {
         try {
             final DecodedJWT jwt = JWT.decode(authToken);
diff --git a/fda-authentication-service/services/src/main/java/at/tuwien/config/WebSecurityConfig.java b/fda-authentication-service/services/src/main/java/at/tuwien/config/WebSecurityConfig.java
index ac11ba38db64d0aeb6a9a3944e53dd850f53365d..56851d0743df3b0a4fd6c15b6e12f28e89a53c42 100644
--- a/fda-authentication-service/services/src/main/java/at/tuwien/config/WebSecurityConfig.java
+++ b/fda-authentication-service/services/src/main/java/at/tuwien/config/WebSecurityConfig.java
@@ -2,6 +2,7 @@ package at.tuwien.config;
 
 import at.tuwien.auth.AuthTokenFilter;
 import at.tuwien.auth.JwtUtils;
+import at.tuwien.service.TokenService;
 import at.tuwien.service.impl.UserDetailsServiceImpl;
 import io.swagger.v3.oas.annotations.enums.SecuritySchemeType;
 import io.swagger.v3.oas.annotations.security.SecurityScheme;
@@ -35,20 +36,22 @@ import javax.servlet.http.HttpServletResponse;
 public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
 
     private final JwtUtils jwtUtils;
+    private final TokenService tokenService;
     private final SecurityConfig securityConfig;
     private final UserDetailsServiceImpl userDetailsService;
 
     @Autowired
-    public WebSecurityConfig(JwtUtils jwtUtils, SecurityConfig securityConfig,
+    public WebSecurityConfig(JwtUtils jwtUtils, TokenService tokenService, SecurityConfig securityConfig,
                              UserDetailsServiceImpl userDetailsService) {
         this.jwtUtils = jwtUtils;
+        this.tokenService = tokenService;
         this.securityConfig = securityConfig;
         this.userDetailsService = userDetailsService;
     }
 
     @Bean
     public AuthTokenFilter authTokenFilter() {
-        return new AuthTokenFilter(jwtUtils, userDetailsService);
+        return new AuthTokenFilter(jwtUtils, tokenService, userDetailsService);
     }
 
     @Override
diff --git a/fda-authentication-service/services/src/main/java/at/tuwien/repositories/TokenRepository.java b/fda-authentication-service/services/src/main/java/at/tuwien/repositories/TokenRepository.java
index 998fdcbd14dabadd41dfc1862534adc4e4291860..9e78e638782ac5e0f602383debf8f1cd3a7057b0 100644
--- a/fda-authentication-service/services/src/main/java/at/tuwien/repositories/TokenRepository.java
+++ b/fda-authentication-service/services/src/main/java/at/tuwien/repositories/TokenRepository.java
@@ -17,4 +17,7 @@ public interface TokenRepository extends JpaRepository<Token, Long> {
 
     Optional<Token> findByTokenHash(String tokenHash);
 
+
+    Optional<Token> findByValidTokenHash(@Param("hash") String tokenHash);
+
 }
diff --git a/fda-authentication-service/services/src/main/java/at/tuwien/service/TokenService.java b/fda-authentication-service/services/src/main/java/at/tuwien/service/TokenService.java
index 26d5e4f33d8a2d4b4ae317f9a7527704c19e1982..caeeb8d41f9532c40b167a6758c8f5e542355d12 100644
--- a/fda-authentication-service/services/src/main/java/at/tuwien/service/TokenService.java
+++ b/fda-authentication-service/services/src/main/java/at/tuwien/service/TokenService.java
@@ -5,7 +5,9 @@ import at.tuwien.exception.TokenNotEligableException;
 import at.tuwien.exception.TokenNotFoundException;
 import at.tuwien.exception.UserNotFoundException;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 
+import javax.servlet.ServletException;
 import java.security.Principal;
 import java.util.List;
 
@@ -40,6 +42,15 @@ public interface TokenService {
      */
     Token findOne(String tokenHash) throws TokenNotFoundException;
 
+    /**
+     * Finds a token by id.
+     *
+     * @param id The token id.
+     * @return The token, if successful.
+     * @throws TokenNotFoundException The token was not found in the metadata database.
+     */
+    Token findOne(Long id) throws TokenNotFoundException;
+
     /**
      * Deletes a developer token in the metadata database by hash and user principal.
      *
@@ -49,4 +60,11 @@ public interface TokenService {
      * @throws UserNotFoundException  The user does not exist in the metadata database.
      */
     void delete(String tokenHash, Principal principal) throws TokenNotFoundException, UserNotFoundException;
+
+    /**
+     * Checks if the developer token has not been marked as deleted
+     *
+     * @param jwt The token
+     */
+    void check(String jwt) throws ServletException;
 }
diff --git a/fda-authentication-service/services/src/main/java/at/tuwien/service/impl/TokenServiceImpl.java b/fda-authentication-service/services/src/main/java/at/tuwien/service/impl/TokenServiceImpl.java
index e7f34b4cf7cfaf058b385ffb03a44c4feffe33cb..283109c46703905607a4cbedd786f9dbaec1442a 100644
--- a/fda-authentication-service/services/src/main/java/at/tuwien/service/impl/TokenServiceImpl.java
+++ b/fda-authentication-service/services/src/main/java/at/tuwien/service/impl/TokenServiceImpl.java
@@ -15,6 +15,7 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
+import javax.servlet.ServletException;
 import java.security.Principal;
 import java.time.Instant;
 import java.time.temporal.ChronoUnit;
@@ -80,6 +81,17 @@ public class TokenServiceImpl implements TokenService {
         return optional.get();
     }
 
+    @Override
+    @Transactional(readOnly = true)
+    public Token findOne(Long id) throws TokenNotFoundException {
+        final Optional<Token> optional = tokenRepository.findById(id);
+        if (optional.isEmpty()) {
+            log.error("Failed to find token with id {}", id);
+            throw new TokenNotFoundException("Failed to find token");
+        }
+        return optional.get();
+    }
+
     @Override
     @Transactional
     public void delete(String tokenHash, Principal principal) throws TokenNotFoundException, UserNotFoundException {
@@ -94,4 +106,18 @@ public class TokenServiceImpl implements TokenService {
         log.debug("deleted token {}", token);
     }
 
+    @Override
+    @Transactional
+    public void check(String jwt) throws ServletException {
+        final String tokenHash = JwtUtils.toHash(jwt);
+        final Optional<Token> optional = tokenRepository.findByValidTokenHash(tokenHash);
+        if (optional.isEmpty()) {
+            log.error("Token with hash {} is marked as revoked", tokenHash);
+            throw new ServletException("Token with hash " + tokenHash + " is marked as revoked");
+        }
+        final Token token = optional.get();
+        token.setLastUsed(Instant.now());
+        tokenRepository.save(token);
+    }
+
 }
diff --git a/fda-metadata-db/entities/src/main/java/at/tuwien/entities/user/Token.java b/fda-metadata-db/entities/src/main/java/at/tuwien/entities/user/Token.java
index d1ae37cfab91fd59117b12602a5ceece3f167392..edf7ae144c28a4cab9b87e25067af12800bf0019 100644
--- a/fda-metadata-db/entities/src/main/java/at/tuwien/entities/user/Token.java
+++ b/fda-metadata-db/entities/src/main/java/at/tuwien/entities/user/Token.java
@@ -3,7 +3,6 @@ package at.tuwien.entities.user;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.*;
 import org.hibernate.annotations.GenericGenerator;
-import org.hibernate.annotations.SQLDelete;
 import org.springframework.data.annotation.CreatedDate;
 import org.springframework.data.jpa.domain.support.AuditingEntityListener;
 
@@ -19,6 +18,11 @@ import java.time.Instant;
 @EntityListeners(AuditingEntityListener.class)
 @EqualsAndHashCode(onlyExplicitlyIncluded = true)
 @Table(name = "mdb_tokens")
+@NamedNativeQueries({
+        @NamedNativeQuery(name = "Token.findByValidTokenHash",
+                query = "SELECT * FROM `mdb_valid_tokens` WHERE `token_hash` = :hash",
+                resultClass = Token.class)
+})
 public class Token {
 
     @Id
@@ -46,7 +50,7 @@ public class Token {
     @Column(nullable = false, updatable = false)
     private Instant expires;
 
-    @Column(nullable = false, updatable = false)
+    @Column
     private Instant lastUsed;
 
 }
diff --git a/fda-metadata-db/setup-schema.sql b/fda-metadata-db/setup-schema.sql
index b58d7cde3db2e599c719bd5fbdfe98606970c9a6..e48b9dbd1737808f2399f866e6dc309f8fa9abd1 100644
--- a/fda-metadata-db/setup-schema.sql
+++ b/fda-metadata-db/setup-schema.sql
@@ -23,7 +23,7 @@ CREATE TABLE IF NOT EXISTS mdb_users
     UNIQUE (username),
     UNIQUE (Main_Email),
     UNIQUE (OID)
-);
+) WITH SYSTEM VERSIONING;
 
 CREATE TABLE mdb_images
 (
@@ -41,7 +41,7 @@ CREATE TABLE mdb_images
     last_modified timestamp,
     PRIMARY KEY (id),
     UNIQUE (repository, tag)
-);
+) WITH SYSTEM VERSIONING;
 
 CREATE TABLE mdb_time_secrets
 (
@@ -53,7 +53,7 @@ CREATE TABLE mdb_time_secrets
     valid_to  timestamp              NOT NULL,
     PRIMARY KEY (id),
     FOREIGN KEY (uid) REFERENCES mdb_users (UserID)
-);
+) WITH SYSTEM VERSIONING;
 
 CREATE TABLE mdb_tokens
 (
@@ -65,7 +65,7 @@ CREATE TABLE mdb_tokens
     last_used  timestamp,
     PRIMARY KEY (id),
     FOREIGN KEY (creator) REFERENCES mdb_users (UserID)
-);
+) WITH SYSTEM VERSIONING;
 
 CREATE TABLE mdb_images_date
 (
@@ -79,7 +79,7 @@ CREATE TABLE mdb_images_date
     PRIMARY KEY (id),
     FOREIGN KEY (iid) REFERENCES mdb_images (id),
     UNIQUE (database_format, unix_format, example)
-);
+) WITH SYSTEM VERSIONING;
 
 CREATE TABLE IF NOT EXISTS mdb_containers
 (
@@ -96,7 +96,7 @@ CREATE TABLE IF NOT EXISTS mdb_containers
     PRIMARY KEY (id),
     FOREIGN KEY (created_by) REFERENCES mdb_users (UserID),
     FOREIGN KEY (image_id) REFERENCES mdb_images (id)
-);
+) WITH SYSTEM VERSIONING;
 
 CREATE TABLE mdb_images_environment_item
 (
@@ -109,7 +109,7 @@ CREATE TABLE mdb_images_environment_item
     last_modified timestamp,
     PRIMARY KEY (id, iid),
     FOREIGN KEY (iid) REFERENCES mdb_images (id)
-);
+) WITH SYSTEM VERSIONING;
 
 CREATE TABLE IF NOT EXISTS mdb_data
 (
@@ -120,7 +120,7 @@ CREATE TABLE IF NOT EXISTS mdb_data
     Version      TEXT,
     Seperator    TEXT,
     PRIMARY KEY (ID)
-);
+) WITH SYSTEM VERSIONING
 
 CREATE TABLE IF NOT EXISTS mdb_user_roles
 (
@@ -131,7 +131,7 @@ CREATE TABLE IF NOT EXISTS mdb_user_roles
     PRIMARY KEY (uid),
     FOREIGN KEY (uid) REFERENCES mdb_users (UserID),
     UNIQUE (uid, role)
-);
+) WITH SYSTEM VERSIONING;
 
 CREATE TABLE IF NOT EXISTS mdb_licenses
 (
@@ -139,7 +139,7 @@ CREATE TABLE IF NOT EXISTS mdb_licenses
     uri        TEXT                   NOT NULL,
     PRIMARY KEY (identifier),
     UNIQUE (uri)
-);
+) WITH SYSTEM VERSIONING;
 
 CREATE TABLE IF NOT EXISTS mdb_databases
 (
@@ -158,14 +158,14 @@ CREATE TABLE IF NOT EXISTS mdb_databases
     FOREIGN KEY (created_by) REFERENCES mdb_users (UserID),
     FOREIGN KEY (contact_person) REFERENCES mdb_users (UserID),
     FOREIGN KEY (id) REFERENCES mdb_containers (id) /* currently we only support one-to-one */
-);
+) WITH SYSTEM VERSIONING;
 
 CREATE TABLE IF NOT EXISTS mdb_databases_subjects
 (
     dbid     BIGINT                 NOT NULL,
     subjects character varying(255) NOT NULL,
     PRIMARY KEY (dbid, subjects)
-);
+) WITH SYSTEM VERSIONING;
 
 CREATE TABLE IF NOT EXISTS mdb_tables
 (
@@ -190,7 +190,7 @@ CREATE TABLE IF NOT EXISTS mdb_tables
     PRIMARY KEY (ID, tDBID),
     FOREIGN KEY (created_by) REFERENCES mdb_users (UserID),
     FOREIGN KEY (tDBID) REFERENCES mdb_databases (id)
-);
+) WITH SYSTEM VERSIONING;
 
 CREATE TABLE IF NOT EXISTS mdb_columns
 (
@@ -215,7 +215,7 @@ CREATE TABLE IF NOT EXISTS mdb_columns
     FOREIGN KEY (cDBID, tID) REFERENCES mdb_tables (tDBID, ID),
     FOREIGN KEY (created_by) REFERENCES mdb_users (UserID),
     PRIMARY KEY (ID, cDBID, tID)
-);
+) WITH SYSTEM VERSIONING;
 
 CREATE TABLE IF NOT EXISTS mdb_columns_enums
 (
@@ -228,7 +228,7 @@ CREATE TABLE IF NOT EXISTS mdb_columns_enums
     last_modified timestamp,
     FOREIGN KEY (eDBID, tID, cID) REFERENCES mdb_columns (cDBID, tID, ID),
     PRIMARY KEY (ID, eDBID, tID, cID)
-);
+) WITH SYSTEM VERSIONING;
 
 CREATE TABLE IF NOT EXISTS mdb_columns_nom
 (
@@ -240,7 +240,7 @@ CREATE TABLE IF NOT EXISTS mdb_columns_nom
     created       timestamp NOT NULL DEFAULT NOW(),
     FOREIGN KEY (cDBID, tID, cID) REFERENCES mdb_columns (cDBID, tID, ID),
     PRIMARY KEY (cDBID, tID, cID)
-);
+) WITH SYSTEM VERSIONING;
 
 CREATE TABLE IF NOT EXISTS mdb_columns_num
 (
@@ -258,7 +258,7 @@ CREATE TABLE IF NOT EXISTS mdb_columns_num
     created       timestamp NOT NULL DEFAULT NOW(),
     FOREIGN KEY (cDBID, tID, cID) REFERENCES mdb_columns (cDBID, tID, ID),
     PRIMARY KEY (cDBID, tID, cID)
-);
+) WITH SYSTEM VERSIONING;
 
 CREATE TABLE IF NOT EXISTS mdb_columns_cat
 (
@@ -271,7 +271,7 @@ CREATE TABLE IF NOT EXISTS mdb_columns_cat
     created       timestamp NOT NULL DEFAULT NOW(),
     FOREIGN KEY (cDBID, tID, cID) REFERENCES mdb_columns (cDBID, tID, ID),
     PRIMARY KEY (cDBID, tID, cID)
-);
+) WITH SYSTEM VERSIONING;
 
 CREATE TABLE IF NOT EXISTS mdb_concepts
 (
@@ -282,7 +282,7 @@ CREATE TABLE IF NOT EXISTS mdb_concepts
     created_by bigint,
     FOREIGN KEY (created_by) REFERENCES mdb_users (UserID),
     PRIMARY KEY (id)
-);
+) WITH SYSTEM VERSIONING;
 
 CREATE TABLE IF NOT EXISTS mdb_columns_concepts
 (
@@ -295,7 +295,7 @@ CREATE TABLE IF NOT EXISTS mdb_columns_concepts
     FOREIGN KEY (cDBID, tID, cID) REFERENCES mdb_columns (cDBID, tID, ID),
     FOREIGN KEY (concept_id) REFERENCES mdb_concepts (id),
     PRIMARY KEY (cDBID, tID, cID)
-);
+) WITH SYSTEM VERSIONING;
 
 CREATE TABLE IF NOT EXISTS mdb_view
 (
@@ -315,7 +315,7 @@ CREATE TABLE IF NOT EXISTS mdb_view
     FOREIGN KEY (created_by) REFERENCES mdb_users (UserID),
     FOREIGN KEY (vdbid) REFERENCES mdb_databases (id),
     PRIMARY KEY (id, vdbid)
-);
+) WITH SYSTEM VERSIONING;
 
 CREATE TABLE IF NOT EXISTS mdb_identifiers
 (
@@ -348,7 +348,7 @@ CREATE TABLE IF NOT EXISTS mdb_identifiers
     FOREIGN KEY (dbid) REFERENCES mdb_databases (id),
     FOREIGN KEY (created_by) REFERENCES mdb_users (UserID),
     UNIQUE (cid, dbid, qid)
-);
+) WITH SYSTEM VERSIONING;
 
 CREATE TABLE IF NOT EXISTS mdb_related_identifiers
 (
@@ -363,7 +363,7 @@ CREATE TABLE IF NOT EXISTS mdb_related_identifiers
     PRIMARY KEY (id, iid), /* must be a single id from persistent identifier concept */
     FOREIGN KEY (iid) REFERENCES mdb_identifiers (id),
     FOREIGN KEY (created_by) REFERENCES mdb_users (UserID)
-);
+) WITH SYSTEM VERSIONING;
 
 CREATE TABLE IF NOT EXISTS mdb_creators
 (
@@ -378,7 +378,7 @@ CREATE TABLE IF NOT EXISTS mdb_creators
     FOREIGN KEY (created_by) REFERENCES mdb_users (UserID),
     PRIMARY KEY (id, pid),
     FOREIGN KEY (pid) REFERENCES mdb_identifiers (id)
-);
+) WITH SYSTEM VERSIONING;
 
 CREATE TABLE IF NOT EXISTS mdb_feed
 (
@@ -389,7 +389,7 @@ CREATE TABLE IF NOT EXISTS mdb_feed
     created timestamp NOT NULL DEFAULT NOW(),
     FOREIGN KEY (fDBID, fID) REFERENCES mdb_tables (tDBID, ID),
     PRIMARY KEY (fDBID, fID, fUserId, fDataID)
-);
+) WITH SYSTEM VERSIONING;
 
 CREATE TABLE IF NOT EXISTS mdb_update
 (
@@ -397,7 +397,7 @@ CREATE TABLE IF NOT EXISTS mdb_update
     uDBID   bigint REFERENCES mdb_databases (id),
     created timestamp NOT NULL DEFAULT NOW(),
     PRIMARY KEY (uUserID, uDBID)
-);
+) WITH SYSTEM VERSIONING;
 
 CREATE TABLE IF NOT EXISTS mdb_access
 (
@@ -407,7 +407,7 @@ CREATE TABLE IF NOT EXISTS mdb_access
     download BOOLEAN,
     created  timestamp NOT NULL DEFAULT NOW(),
     PRIMARY KEY (aUserID, aDBID)
-);
+) WITH SYSTEM VERSIONING
 
 CREATE TABLE IF NOT EXISTS mdb_have_access
 (
@@ -416,7 +416,7 @@ CREATE TABLE IF NOT EXISTS mdb_have_access
     hType   ENUM ('R', 'W'),
     created timestamp NOT NULL DEFAULT NOW(),
     PRIMARY KEY (hUserID, hDBID)
-);
+) WITH SYSTEM VERSIONING;
 
 CREATE TABLE IF NOT EXISTS mdb_owns
 (
@@ -424,7 +424,18 @@ CREATE TABLE IF NOT EXISTS mdb_owns
     oDBID   bigint REFERENCES mdb_databases (ID),
     created timestamp NOT NULL DEFAULT NOW(),
     PRIMARY KEY (oUserID, oDBID)
-);
+) WITH SYSTEM VERSIONING;
+
+CREATE VIEW IF NOT EXISTS mdb_valid_tokens AS
+(
+SELECT `id`, `token_hash`, `creator`, `created`, `expires`, `last_used`
+FROM (SELECT `id`, `token_hash`, `creator`, `created`, `expires`, `last_used`
+      FROM `mdb_tokens` FOR SYSTEM_TIME ALL) as t
+WHERE NOT EXISTS(SELECT `token_hash`
+             FROM mdb_tokens AS tt
+             WHERE ROW_END > NOW()
+               AND tt.`token_hash` = t.`token_hash`)
+GROUP BY `id`);
 
 COMMIT;
 
diff --git a/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/AbstractEndpoint.java b/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/AbstractEndpoint.java
index 8da23987b629668dc2cd1fa679775771c270bf7f..af0232f142d04469d981aedf100e7aa8b1e4ebde 100644
--- a/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/AbstractEndpoint.java
+++ b/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/AbstractEndpoint.java
@@ -1,21 +1,25 @@
 package at.tuwien.endpoint;
 
 import at.tuwien.SortType;
+import at.tuwien.api.database.query.ExecuteStatementDto;
 import at.tuwien.entities.database.Database;
 import at.tuwien.entities.database.table.Table;
 import at.tuwien.entities.identifier.Identifier;
-import at.tuwien.exception.DatabaseNotFoundException;
-import at.tuwien.exception.IdentifierNotFoundException;
-import at.tuwien.exception.PaginationException;
-import at.tuwien.exception.SortException;
+import at.tuwien.exception.*;
 import at.tuwien.service.DatabaseService;
 import at.tuwien.service.IdentifierService;
 import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.io.FileUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.core.Authentication;
 
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.Charset;
 import java.security.Principal;
 import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 import static at.tuwien.entities.identifier.VisibilityType.EVERYONE;
 
@@ -99,6 +103,25 @@ public abstract class AbstractEndpoint {
         }
     }
 
+    protected void validateForbiddenStatements(ExecuteStatementDto data) throws QueryMalformedException,
+            QueryStoreException {
+        final StringBuilder regex = new StringBuilder("[");
+        try {
+            FileUtils.readLines(new File("src/main/resources/forbidden.txt"), Charset.defaultCharset())
+                    .forEach(regex::append);
+        } catch (IOException e) {
+            log.error("Failed to load forbidden keywords list, reason {}", e.getMessage());
+            throw new QueryStoreException("Failed to load forbidden keywords list", e);
+        }
+        final Pattern pattern = Pattern.compile(regex + "]");
+        final Matcher matcher = pattern.matcher(data.getStatement());
+        final boolean found = matcher.find();
+        if (found) {
+            log.error("Query contains blacklisted character");
+            throw new QueryMalformedException("Query contains blacklisted character");
+        }
+    }
+
     protected Boolean hasQueuePermission(Long containerId, Long databaseId, Long tableId, String permissionCode,
                                          Principal principal) {
         log.trace("validate queue permission, containerId={}, databaseId={}, tableId={}, permissionCode={}, principal={}",
diff --git a/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/QueryEndpoint.java b/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/QueryEndpoint.java
index 0699f5b758213fc5e8cc60c280eed63fe16bae81..3abdccb05c4079688a4baa84a60dd778f1156d0d 100644
--- a/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/QueryEndpoint.java
+++ b/fda-query-service/rest-service/src/main/java/at/tuwien/endpoint/QueryEndpoint.java
@@ -20,6 +20,8 @@ import org.springframework.web.bind.annotation.*;
 import javax.validation.Valid;
 import javax.validation.constraints.NotNull;
 import java.security.Principal;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 @Log4j2
 @RestController
@@ -63,6 +65,7 @@ public class QueryEndpoint extends AbstractEndpoint {
             log.error("Failed to execute query: is empty");
             throw new QueryMalformedException("Failed to execute query");
         }
+        validateForbiddenStatements(data);
         validateDataParams(page, size, sortDirection, sortColumn);
         /* execute */
         final QueryResultDto result = queryService.execute(containerId, databaseId, data, QueryTypeDto.QUERY,
diff --git a/fda-query-service/rest-service/src/main/resources/forbidden.txt b/fda-query-service/rest-service/src/main/resources/forbidden.txt
new file mode 100644
index 0000000000000000000000000000000000000000..89bdcae71069ee0e80035dbc2a0143d570ab253d
--- /dev/null
+++ b/fda-query-service/rest-service/src/main/resources/forbidden.txt
@@ -0,0 +1 @@
+ \*
\ No newline at end of file
diff --git a/fda-query-service/rest-service/src/test/java/at/tuwien/BaseUnitTest.java b/fda-query-service/rest-service/src/test/java/at/tuwien/BaseUnitTest.java
index 2045c6a86f5c6833138891efd70514f512edc902..4c268cc350d5a2f158ee15b446a36acf8308cb5d 100644
--- a/fda-query-service/rest-service/src/test/java/at/tuwien/BaseUnitTest.java
+++ b/fda-query-service/rest-service/src/test/java/at/tuwien/BaseUnitTest.java
@@ -2,6 +2,8 @@ package at.tuwien;
 
 import at.tuwien.api.database.query.QueryBriefDto;
 import at.tuwien.api.database.query.QueryDto;
+import at.tuwien.api.database.query.QueryResultDto;
+import at.tuwien.api.user.UserDetailsDto;
 import at.tuwien.api.user.UserDto;
 import at.tuwien.entities.container.image.ContainerImageDate;
 import at.tuwien.entities.database.table.columns.concepts.Concept;
@@ -16,11 +18,17 @@ import at.tuwien.entities.database.Database;
 import at.tuwien.entities.database.table.Table;
 import at.tuwien.entities.database.table.columns.TableColumn;
 import at.tuwien.entities.database.table.columns.TableColumnType;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
 import org.springframework.test.context.TestPropertySource;
 
+import java.security.Principal;
 import java.time.Instant;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 import static java.time.temporal.ChronoUnit.*;
 
@@ -30,27 +38,41 @@ public abstract class BaseUnitTest {
     public final static long USER_1_ID = 1;
     public final static String USER_1_USERNAME = "junit";
     public final static String USER_1_EMAIL = "junit@example.com";
+    public final static String USER_1_PASSWORD = "password";
+
     public final static Instant USER_1_CREATED = Instant.now().minus(1, HOURS);
+
     public final static User USER_1 = User.builder()
             .id(USER_1_ID)
             .username(USER_1_USERNAME)
             .email(USER_1_EMAIL)
             .emailVerified(true)
             .themeDark(false)
-            .password("password")
+            .password(USER_1_PASSWORD)
             .roles(Collections.singletonList(RoleType.ROLE_RESEARCHER))
             .created(USER_1_CREATED)
             .lastModified(USER_1_CREATED)
             .build();
+
     public final static UserDto USER_1_DTO = UserDto.builder()
             .id(USER_1_ID)
             .username(USER_1_USERNAME)
             .email(USER_1_EMAIL)
             .emailVerified(true)
             .themeDark(false)
-            .password("password")
+            .password(USER_1_PASSWORD)
             .build();
 
+    public final static UserDetails USER_1_DETAILS = UserDetailsDto.builder()
+            .username(USER_1_USERNAME)
+            .email(USER_1_EMAIL)
+            .password(USER_1_PASSWORD)
+            .authorities(List.of(new SimpleGrantedAuthority("ROLE_RESEARCHER")))
+            .build();
+
+    public final static Principal USER_1_PRINCIPAL = new UsernamePasswordAuthenticationToken(USER_1_DETAILS,
+            USER_1_PASSWORD, USER_1_DETAILS.getAuthorities());
+
     public final static String DATABASE_NET = "fda-userdb";
 
     public final static String BROKER_IMAGE = "fda-broker-service:latest";
@@ -1893,4 +1915,24 @@ public abstract class BaseUnitTest {
             .exchange(DATABASE_3_EXCHANGE)
             .build();
 
+    public final static Long QUERY_1_RESULT_ID = 1L;
+    public final static Long QUERY_1_RESULT_NUMBER = 2L;
+    public final static List<Map<String, Object>> QUERY_1_RESULT_RESULT = List.of(
+            new HashMap<>() {{
+                put("location", "Albury");
+                put("lat", -36.0653583);
+                put("lng", 146.9112214);
+            }}, new HashMap<>() {{
+                put("location", "Sydney");
+                put("lat", -33.847927);
+                put("lng", 150.6517942);
+            }});
+
+    public final static QueryResultDto QUERY_1_RESULT_DTO = QueryResultDto.builder()
+            .id(QUERY_1_RESULT_ID)
+            .resultNumber(QUERY_1_RESULT_NUMBER)
+            .result(QUERY_1_RESULT_RESULT)
+            .build();
+
+
 }
diff --git a/fda-query-service/rest-service/src/test/java/at/tuwien/endpoint/QueryEndpointUnitTest.java b/fda-query-service/rest-service/src/test/java/at/tuwien/endpoint/QueryEndpointUnitTest.java
index 7d53a289675c58b64d13628c7b8aca173d1920ad..86d07a94fa9b329b844cb7f32906f9d12bb51443 100644
--- a/fda-query-service/rest-service/src/test/java/at/tuwien/endpoint/QueryEndpointUnitTest.java
+++ b/fda-query-service/rest-service/src/test/java/at/tuwien/endpoint/QueryEndpointUnitTest.java
@@ -1,15 +1,34 @@
 package at.tuwien.endpoint;
 
 import at.tuwien.BaseUnitTest;
+import at.tuwien.SortType;
+import at.tuwien.api.database.query.ExecuteStatementDto;
+import at.tuwien.api.database.query.QueryResultDto;
+import at.tuwien.api.database.query.QueryTypeDto;
 import at.tuwien.config.ReadyConfig;
+import at.tuwien.exception.*;
 import at.tuwien.listener.impl.RabbitMqListenerImpl;
+import at.tuwien.repository.jpa.ContainerRepository;
+import at.tuwien.repository.jpa.DatabaseRepository;
+import at.tuwien.repository.jpa.ImageRepository;
+import at.tuwien.service.QueryService;
+import at.tuwien.service.StoreService;
 import com.rabbitmq.client.Channel;
 import lombok.extern.log4j.Log4j2;
+import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
 import org.springframework.test.context.junit.jupiter.SpringExtension;
 
+import java.util.Optional;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.when;
+
 @Log4j2
 @SpringBootTest
 @ExtendWith(SpringExtension.class)
@@ -24,4 +43,75 @@ public class QueryEndpointUnitTest extends BaseUnitTest {
     @MockBean
     private RabbitMqListenerImpl rabbitMqListener;
 
+    @MockBean
+    private ImageRepository imageRepository;
+
+    @MockBean
+    private ContainerRepository containerRepository;
+
+    @MockBean
+    private DatabaseRepository databaseRepository;
+
+    @MockBean
+    private QueryService queryService;
+
+    @MockBean
+    private StoreService storeService;
+
+    @Autowired
+    private QueryEndpoint queryEndpoint;
+
+    @Test
+    public void execute_forbiddenKeyword_fails() throws UserNotFoundException, QueryStoreException,
+            TableMalformedException, DatabaseConnectionException, QueryMalformedException, ColumnParseException,
+            DatabaseNotFoundException, ImageNotSupportedException, ContainerNotFoundException {
+        final ExecuteStatementDto request = ExecuteStatementDto.builder()
+                .statement("SELECT w.* FROM `weather_aus` w")
+                .build();
+        final Long page = 0L;
+        final Long size = 2L;
+        final SortType sortDirection = SortType.ASC;
+        final String sortColumn = "location";
+
+        /* mock */
+        when(databaseRepository.findByContainerIdAndDatabaseId(CONTAINER_1_ID, DATABASE_1_ID))
+                .thenReturn(Optional.of(DATABASE_1));
+        when(queryService.execute(CONTAINER_1_ID, DATABASE_1_ID, request, QueryTypeDto.QUERY,
+                USER_1_PRINCIPAL, page, size, sortDirection, sortColumn))
+                .thenReturn(QUERY_1_RESULT_DTO);
+
+        /* test */
+        assertThrows(QueryMalformedException.class, () -> {
+            queryEndpoint.execute(CONTAINER_1_ID, DATABASE_1_ID, request, page, size, USER_1_PRINCIPAL, sortDirection,
+                    sortColumn);
+        });
+    }
+
+    @Test
+    public void execute_forbiddenKeyword2_fails() throws UserNotFoundException, QueryStoreException,
+            TableMalformedException, DatabaseConnectionException, QueryMalformedException, ColumnParseException,
+            DatabaseNotFoundException, ImageNotSupportedException, ContainerNotFoundException {
+        final ExecuteStatementDto request = ExecuteStatementDto.builder()
+                .statement("SELECT * FROM `weather_aus` w")
+                .build();
+        final Long page = 0L;
+        final Long size = 2L;
+        final SortType sortDirection = SortType.ASC;
+        final String sortColumn = "location";
+
+        /* mock */
+        when(databaseRepository.findByContainerIdAndDatabaseId(CONTAINER_1_ID, DATABASE_1_ID))
+                .thenReturn(Optional.of(DATABASE_1));
+        when(queryService.execute(CONTAINER_1_ID, DATABASE_1_ID, request, QueryTypeDto.QUERY,
+                USER_1_PRINCIPAL, page, size, sortDirection, sortColumn))
+                .thenReturn(QUERY_1_RESULT_DTO);
+
+        /* test */
+        assertThrows(QueryMalformedException.class, () -> {
+            queryEndpoint.execute(CONTAINER_1_ID, DATABASE_1_ID, request, page, size, USER_1_PRINCIPAL, sortDirection,
+                    sortColumn);
+        });
+    }
+
+
 }
diff --git a/fda-ui/layouts/default.vue b/fda-ui/layouts/default.vue
index 63c821f9eed411adbe629b3af998f1f07b0c4cd0..919a3359c3cc76f21e02575667be36cb98e5ac7c 100644
--- a/fda-ui/layouts/default.vue
+++ b/fda-ui/layouts/default.vue
@@ -263,8 +263,8 @@ export default {
       }
     },
     login () {
-      let redirect = ![undefined ,'/', '/login'].includes(this.$router.currentRoute.path)
-      this.$router.push({ path: '/login', query: redirect ? { redirect: this.$router.currentRoute.path } : {}})
+      const redirect = ![undefined, '/', '/login'].includes(this.$router.currentRoute.path)
+      this.$router.push({ path: '/login', query: redirect ? { redirect: this.$router.currentRoute.path } : {} })
     },
     navigate (item) {
       this.$router.push(this.metadata(item).link)
diff --git a/fda-ui/nuxt.config.js b/fda-ui/nuxt.config.js
index 08afe5b2e157211bc6a6352dd5343badea42db39..902ab4c355e844468c25af4a737178b917ab9560 100644
--- a/fda-ui/nuxt.config.js
+++ b/fda-ui/nuxt.config.js
@@ -77,7 +77,8 @@ export default {
     sharedFilesystem: process.env.SHARED_FILESYSTEM || '/tmp',
     version: process.env.VERSION || 'latest',
     logo: process.env.LOGO || '/logo.png',
-    mailVerify: process.env.MAIL_VERIFY || false
+    mailVerify: process.env.MAIL_VERIFY || false,
+    tokenMax: process.env.TOKEN_MAX || 5
   },
 
   proxy: {
diff --git a/fda-ui/pages/login.vue b/fda-ui/pages/login.vue
index b9b82425b216eb84ac34d9bedfbdef1765e72778..caf8bf208eb8768656cd7a62bccc8f5ea3af974d 100644
--- a/fda-ui/pages/login.vue
+++ b/fda-ui/pages/login.vue
@@ -96,7 +96,7 @@ export default {
         delete user.token
         this.$store.commit('SET_USER', user)
         this.$toast.success('Welcome back!')
-        this.$router.push(this.$route.query.redirect ?  this.$route.query.redirect : '/container')
+        this.$router.push(this.$route.query.redirect ? this.$route.query.redirect : '/container')
       } catch (err) {
         if (err.response !== undefined && err.response.status !== undefined) {
           if (err.response.status === 418) {
diff --git a/fda-ui/pages/user/developer.vue b/fda-ui/pages/user/developer.vue
index bdc5f38bfe235be6489c3348793df1be5390645f..9dc9bf35fade6bf5784ef4116e9b62a69c0646e8 100644
--- a/fda-ui/pages/user/developer.vue
+++ b/fda-ui/pages/user/developer.vue
@@ -9,9 +9,10 @@
           <v-card-text>
             <v-list-item v-for="(item, i) in tokens" :key="i" three-line>
               <v-list-item-content>
-                <v-list-item-title>sha256:{{ item.token_hash }}</v-list-item-title>
-                <v-list-item-subtitle v-if="!item.token">
-                  Created on {{ format(item.created) }}, Valid until: {{ format(item.expires) }}</v-list-item-subtitle>
+                <v-list-item-title :class="tokenClass(item)">sha256:{{ item.token_hash }}</v-list-item-title>
+                <v-list-item-subtitle v-if="!item.token" :class="tokenClass(item)">
+                  Last used: <span v-if="item.last_used">{{ format(item.last_used) }}</span><span v-if="!item.last_used">Never</span> &mdash; valid until: {{ format(item.expires) }}
+                </v-list-item-subtitle>
                 <v-list-item-subtitle v-if="item.token">
                   <v-text-field
                     v-model="item.token"
@@ -23,12 +24,12 @@
                     @click:append-outer="copy(item)" />
                 </v-list-item-subtitle>
                 <v-list-item-subtitle v-if="!item.token">
-                  <a @click="revokeToken(item.token_hash)">Revoke Token</a>
+                  <a @click="revokeToken(item.id)">Revoke Token</a>
                 </v-list-item-subtitle>
               </v-list-item-content>
             </v-list-item>
-            <v-btn class="mt-4" x-small @click="mintToken">
-              Mint Token
+            <v-btn :disabled="tokens.length >= tokenMax" class="mt-4" color="secondary" small @click="mintToken">
+              Create Token
             </v-btn>
           </v-card-text>
         </v-card>
@@ -59,6 +60,9 @@ export default {
       return {
         headers: { Authorization: `Bearer ${this.token}` }
       }
+    },
+    tokenMax () {
+      return this.$config.tokenMax
     }
   },
   mounted () {
@@ -74,6 +78,9 @@ export default {
     format (timestamp) {
       return formatTimestamp(timestamp)
     },
+    tokenClass (token) {
+      return token.last_used ? '' : 'token-not_used'
+    },
     async loadTokens () {
       this.loading = true
       try {
@@ -102,10 +109,10 @@ export default {
       }
       this.loading = false
     },
-    async revokeToken (hash) {
+    async revokeToken (id) {
       this.loading = true
       try {
-        await this.$axios.delete(`/api/user/token/${hash}`, this.config)
+        await this.$axios.delete(`/api/user/token/${id}`, this.config)
         await this.loadTokens()
       } catch (err) {
         this.$toast.error('Could not delete token')
@@ -115,3 +122,8 @@ export default {
   }
 }
 </script>
+<style>
+.token-not_used {
+  opacity: 0.4;
+}
+</style>