diff --git a/README.md b/README.md
index 052afc1..058e7c5 100644
--- a/README.md
+++ b/README.md
@@ -29,7 +29,7 @@ Spring MVC:
- favicon handler
Security:
-- Account management
+- Account management with KeyCloak
- Spring Security
- User/User_Authority entity and repository/services
- login, logout, home pages based on user role
@@ -108,12 +108,12 @@ It contains following applications:
# Note you will need to create a database named 'seedapp' in your mysql server
-Option 1 - run with manually started ActiveMQ and MySQL servers
+Option 1 - run with manually started KeyCloak, ActiveMQ and MySQL servers
- Run ```mvn clean install``` at root
- Run ```docker-compose -f config/docker-compose.yml up``` at root to start docker containers
- Go to main-app folder and run ```mvn``` to start the application
-Option 2 - automatically start ActiveMQ and MySQL using TestContainer while application is starting
+Option 2 - automatically start KeyCloak, ActiveMQ and MySQL using TestContainer while application is starting
- Run ```mvn clean install``` at root
- Go to main-app folder and run ```mvn -Pdev,withTestContainer``` to start the application
diff --git a/config/docker-compose.yml b/config/docker-compose.yml
index 510c852..923adde 100644
--- a/config/docker-compose.yml
+++ b/config/docker-compose.yml
@@ -38,6 +38,25 @@ services:
- 9411:9411
networks:
- seedappnet
+ keycloak:
+ image: 'quay.io/keycloak/keycloak:23.0.3'
+ container_name: keycloak
+ command: start-dev --import-realm
+ environment:
+ - KEYCLOAK_DB=dev-file
+ - KEYCLOAK_ADMIN=admin
+ - KEYCLOAK_ADMIN_PASSWORD=admin
+ - KEYCLOAK_FEATURES=scripts
+ - KEYCLOAK_HTTP_PORT=8080
+ - KEYCLOAK_HTTPS_PORT=9443
+ # entrypoint: /tmp/keycloak/config/docker-compose-entrypoint.sh --hostname host.docker.internal:8080
+ volumes:
+ - ../main-app/main-webapp/src/main/resources/keycloak/:/opt/keycloak/data/import
+ ports:
+ - 8082:8080
+ - 9443:9443
+ networks:
+ - seedappnet
volumes:
esdata1:
driver: local
diff --git a/email/email-service/pom.xml b/email/email-service/pom.xml
index dad184b..d56dd43 100755
--- a/email/email-service/pom.xml
+++ b/email/email-service/pom.xml
@@ -145,14 +145,6 @@
true
-
-
- org.springdoc
- springdoc-openapi-starter-webmvc-ui
- ${springdoc-openapi-ui.version}
- true
-
-
dev
dev
diff --git a/email/email-service/src/main/java/gt/mail/web/rest/HelloResource.java b/email/email-service/src/main/java/gt/mail/web/rest/HelloResource.java
index 44a5f43..f9d6994 100755
--- a/email/email-service/src/main/java/gt/mail/web/rest/HelloResource.java
+++ b/email/email-service/src/main/java/gt/mail/web/rest/HelloResource.java
@@ -1,6 +1,6 @@
package gt.mail.web.rest;
-import gt.common.config.BaseException;
+//import gt.common.config.BaseException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
@@ -21,9 +21,9 @@ public class HelloResource {
@GetMapping("/hello")
public Map sayHello() {
log.info("Received hello request");
- if (RANDOM.nextBoolean()) {
- throw new BaseException("Something");
- }
+// if (RANDOM.nextBoolean()) {
+// throw new BaseException("Something");
+// }
return Map.of("hello", "world");
}
}
diff --git a/email/email-service/src/test/java/gt/mail/web/rest/EmailResourceIT.java b/email/email-service/src/test/java/gt/mail/web/rest/EmailResourceIT.java
index 58084e4..f2b74bf 100644
--- a/email/email-service/src/test/java/gt/mail/web/rest/EmailResourceIT.java
+++ b/email/email-service/src/test/java/gt/mail/web/rest/EmailResourceIT.java
@@ -17,16 +17,27 @@
class EmailResourceIT {
@Test
- void sayHello2(@Autowired MockMvc mvc) throws Exception {
+ void canSendEmail(@Autowired MockMvc mvc) throws Exception {
mvc.perform(
- post("/sendEmail")
- .contentType(MediaType.APPLICATION_JSON)
- .content(sampleEmail().getBytes()))
+ post("/sendEmail")
+ .contentType(MediaType.APPLICATION_JSON)
+ .content(sampleEmail().getBytes()))
.andExpect(status().isOk());
}
String sampleEmail() {
- return "{\"from\":\"test@email.com\",\"subject\":\"Test\",\"content\":\"Body\",\"isHtml\":false,\"files\":[],\"to\":[\"recep@emai.com\"],\"cc\":[],\"bcc\":[]}";
+ return """
+ {
+ "fromEmail":"test@email.com",
+ "subject":"Test",
+ "content":"Body",
+ "isHtml":false,
+ "files":[],
+ "to":["recep@emai.com"]
+ ,"cc":[],
+ "bcc":[]
+ }
+ """;
}
}
diff --git a/email/email-service/src/test/resources/application.yml b/email/email-service/src/test/resources/application.yml
index 5f981db..73251db 100755
--- a/email/email-service/src/test/resources/application.yml
+++ b/email/email-service/src/test/resources/application.yml
@@ -12,7 +12,11 @@ spring:
port: ${MAILHOG_PORT:1025}
username:
password:
-
+ security:
+ oauth2:
+ resourceserver:
+ jwt:
+ issuer-uri: http://localhost:${KEYCLOAK_PORT:8082}/realms/articleapp
logging.level:
ROOT: WARN
gt: INFO
diff --git a/main-app/main-orm/pom.xml b/main-app/main-orm/pom.xml
index 204c7f4..86cb2ed 100644
--- a/main-app/main-orm/pom.xml
+++ b/main-app/main-orm/pom.xml
@@ -98,10 +98,15 @@
src/main/resources/liquibase-jpa-diff.properties
-
+
+
+ org.liquibase.ext
+ liquibase-hibernate6
+ ${liquibase.version}
+
org.springframework
spring-beans
diff --git a/main-app/main-orm/src/main/java/gt/app/domain/AppUser.java b/main-app/main-orm/src/main/java/gt/app/domain/AppUser.java
index af9e314..71762f5 100644
--- a/main-app/main-orm/src/main/java/gt/app/domain/AppUser.java
+++ b/main-app/main-orm/src/main/java/gt/app/domain/AppUser.java
@@ -1,225 +1,61 @@
package gt.app.domain;
-import jakarta.persistence.*;
-import jakarta.validation.constraints.Size;
-import lombok.EqualsAndHashCode;
-import org.hibernate.annotations.BatchSize;
-import org.hibernate.annotations.Fetch;
-import org.hibernate.annotations.FetchMode;
-import org.springframework.security.core.userdetails.UserDetails;
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.Id;
+import jakarta.persistence.Table;
+import lombok.Getter;
+import lombok.Setter;
+import org.hibernate.annotations.JdbcTypeCode;
+import org.hibernate.type.SqlTypes;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.Objects;
-import java.util.Set;
+import java.util.UUID;
-@EqualsAndHashCode(callSuper = true)
@Entity
+@Getter
+@Setter
@Table(name = "APP_USER")
-public class AppUser extends BaseEntity implements UserDetails {
- @Basic(fetch = FetchType.LAZY)
- @Lob
- byte[] avatar;
+public class AppUser {
+
+ @Id
+ @JdbcTypeCode(SqlTypes.BINARY)
+ UUID id;
@Column(nullable = false)
- @Size(min = 2, max = 30)
private String firstName;
- @Size(max = 30)
private String lastName;
- @Column(length = 254, unique = true, nullable = false)
- private String email;
-
- @Column(nullable = false, unique = true)
- @Size(min = 5, max = 20)
- private String username;
-
- @Column(name = "password_hash", length = 60)
- private String password;
-
- @ManyToMany(fetch = FetchType.LAZY)
- @JoinTable(
- name = "user_authority",
- joinColumns = {@JoinColumn(name = "user_id", referencedColumnName = "id")},
- inverseJoinColumns = {@JoinColumn(name = "authority_name", referencedColumnName = "name")})
- @Fetch(FetchMode.SUBSELECT)
- @BatchSize(size = 5)
- private Set authorities = new HashSet<>();
-
@Column(nullable = false)
- private Boolean active = false;
+ private String username; //doesn't need to be unique (multi-tenacy)
- @Column(nullable = false)
- private Boolean accountNonExpired;
@Column(nullable = false)
- private Boolean accountNonLocked;
-
- @Column(nullable = false)
- private Boolean credentialsNonExpired;
-
- private String activationKey;
-
- private String resetKey;
-
- @Override
- public Collection getAuthorities() {
- return authorities;
- }
-
- @Override
- public String getPassword() {
- return password;
- }
-
- @Override
- public String getUsername() {
- return username;
- }
-
- @Override
- public boolean isAccountNonExpired() {
- return accountNonExpired;
- }
-
- @Override
- public boolean isAccountNonLocked() {
- return accountNonLocked;
- }
-
- @Override
- public boolean isCredentialsNonExpired() {
- return credentialsNonExpired;
- }
-
- @Override
- public boolean isEnabled() {
- return active;
- }
-
- public byte[] getAvatar() {
- return avatar;
- }
-
- public String getFirstName() {
- return firstName;
- }
-
- public String getLastName() {
- return lastName;
- }
-
- public String getEmail() {
- return email;
- }
-
- public String getActivationKey() {
- return activationKey;
- }
-
- public String getResetKey() {
- return resetKey;
- }
-
- public void setAvatar(byte[] avatar) {
- this.avatar = avatar;
- }
-
- public void setFirstName(String firstName) {
- this.firstName = firstName;
- }
-
- public void setLastName(String lastName) {
- this.lastName = lastName;
- }
-
- public void setEmail(String email) {
- this.email = email;
- }
-
- public void setUsername(String username) {
- this.username = username;
- }
-
- public void setPassword(String password) {
- this.password = password;
- }
-
- public void setAuthorities(Set authorities) {
- this.authorities = authorities;
- }
-
- public void setActive(Boolean active) {
- this.active = active;
- }
-
- public void setAccountNonExpired(Boolean accountNonExpired) {
- this.accountNonExpired = accountNonExpired;
- }
-
- public void setAccountNonLocked(Boolean accountNonLocked) {
- this.accountNonLocked = accountNonLocked;
- }
-
- public void setCredentialsNonExpired(Boolean credentialsNonExpired) {
- this.credentialsNonExpired = credentialsNonExpired;
- }
-
- public void setActivationKey(String activationKey) {
- this.activationKey = activationKey;
- }
-
- public void setResetKey(String resetKey) {
- this.resetKey = resetKey;
- }
+ private String email; //doesn't need to be unique (multi-tenacy)
public AppUser() {
}
- public AppUser(String username, String firstName, String lastName, String email) {
+ public AppUser(UUID id, String username, String firstName, String lastName, String email) {
+ this.id = id;
this.username = username;
this.firstName = firstName;
this.lastName = lastName;
this.email = email;
- this.accountNonExpired = Boolean.TRUE;
- this.accountNonLocked = Boolean.TRUE;
- this.credentialsNonExpired = Boolean.TRUE;
- this.active = Boolean.TRUE;
+ }
+
+ public AppUser(String id, String username, String firstName, String lastName, String email) {
+ this(UUID.fromString(id), username, firstName, lastName, email);
}
@Override
public String toString() {
return "User{" +
+ "id='" + id + '\'' +
+ "username='" + username + '\'' +
"firstName='" + firstName + '\'' +
", lastName='" + lastName + '\'' +
- ", email='" + email + '\'' +
- ", authorities=" + authorities +
- ", active=" + active +
- ", accountNonExpired=" + accountNonExpired +
- ", accountNonLocked=" + accountNonLocked +
- ", credentialsNonExpired=" + credentialsNonExpired +
'}';
}
-
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
- if (!super.equals(o)) {
- return false;
- }
- AppUser appUser = (AppUser) o;
- return Objects.equals(username, appUser.username);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(super.hashCode(), username);
- }
}
diff --git a/main-app/main-orm/src/main/java/gt/app/domain/LiteUser.java b/main-app/main-orm/src/main/java/gt/app/domain/LiteUser.java
deleted file mode 100644
index 318e2ed..0000000
--- a/main-app/main-orm/src/main/java/gt/app/domain/LiteUser.java
+++ /dev/null
@@ -1,36 +0,0 @@
-package gt.app.domain;
-
-import jakarta.persistence.Column;
-import jakarta.persistence.Entity;
-import jakarta.persistence.Table;
-import jakarta.validation.constraints.Size;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-
-@EqualsAndHashCode(callSuper = true)
-@Entity
-@Table(name="APP_USER")
-@Data
-public class LiteUser extends BaseEntity {
-
- @Column(nullable = false)
- @Size(min = 2, max = 30)
- private String firstName;
-
- @Size(max = 30)
- private String lastName;
-
- @Column(length = 254, unique = true, nullable = false)
- private String email;
-
- @Column(nullable = false, unique = true)
- @Size(min = 5, max = 20)
- private String username;
-
- @Column(name = "password_hash", length = 60)
- private String password;
-
- @Column(nullable = false)
- private Boolean active = false;
-
-}
diff --git a/main-app/main-orm/src/main/resources/liquibase-jpa-diff.properties b/main-app/main-orm/src/main/resources/liquibase-jpa-diff.properties
index 555799f..ab39387 100644
--- a/main-app/main-orm/src/main/resources/liquibase-jpa-diff.properties
+++ b/main-app/main-orm/src/main/resources/liquibase-jpa-diff.properties
@@ -5,7 +5,9 @@ driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/seedapp?allowPublicKeyRetrieval=true
username=root
password=password
-referenceUrl=hibernate:spring:gt.app.domain?dialect=org.hibernate.dialect.MySQL8Dialect&hibernate.physical_naming_strategy=gt.app.hibernate.PrefixedNamingStrategy&hibernate.implicit_naming_strategy=org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy
+referenceUrl=hibernate:spring:gt.app.domain?dialect=org.hibernate.dialect.MySQLDialect&hibernate.physical_naming_strategy=gt.app.hibernate.PrefixedNamingStrategy&hibernate.implicit_naming_strategy=org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy
+#referenceUrl=hibernate:spring:gt.app.domain?dialect=${liquibase-plugin.hibernate-dialect}&hibernate.physical_naming_strategy=org.hibernate.boot.model.naming.CamelCaseToUnderscoresNamingStrategy&hibernate.implicit_naming_strategy=org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy
## the default naming strategies are configured here: org.springframework.boot.autoconfigure.orm.jpa.HibernateProperties.Naming.applyNamingStrategies
## we have to map the same for liquibase so that it will generate the column/tables that's compatible with Spring-Data-JPA
+
diff --git a/main-app/main-orm/src/main/resources/liquibase/changelog/20221112224444_changelog.xml b/main-app/main-orm/src/main/resources/liquibase/changelog/20221112224444_changelog.xml
deleted file mode 100644
index 1030c8c..0000000
--- a/main-app/main-orm/src/main/resources/liquibase/changelog/20221112224444_changelog.xml
+++ /dev/null
@@ -1,187 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/main-app/main-orm/src/main/resources/liquibase/changelog/20231231191733_changelog.xml b/main-app/main-orm/src/main/resources/liquibase/changelog/20231231191733_changelog.xml
new file mode 100644
index 0000000..af6c8b4
--- /dev/null
+++ b/main-app/main-orm/src/main/resources/liquibase/changelog/20231231191733_changelog.xml
@@ -0,0 +1,126 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/main-app/main-orm/src/main/resources/liquibase/master.xml b/main-app/main-orm/src/main/resources/liquibase/master.xml
index 34c6b5f..fbcb948 100644
--- a/main-app/main-orm/src/main/resources/liquibase/master.xml
+++ b/main-app/main-orm/src/main/resources/liquibase/master.xml
@@ -4,6 +4,6 @@
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.6.xsd">
-
+
diff --git a/main-app/main-webapp/pom.xml b/main-app/main-webapp/pom.xml
index 05084a5..6ff0447 100644
--- a/main-app/main-webapp/pom.xml
+++ b/main-app/main-webapp/pom.xml
@@ -136,7 +136,15 @@
org.springframework.boot
spring-boot-starter-security
-
+
+
+ org.springframework.boot
+ spring-boot-starter-oauth2-client
+
+
+ org.springframework.boot
+ spring-boot-starter-oauth2-resource-server
+
@@ -226,6 +234,11 @@
+
+ com.github.dasniko
+ testcontainers-keycloak
+ true
+
org.testcontainers
testcontainers
@@ -236,7 +249,6 @@
junit-jupiter
test
-
org.testcontainers
mysql
@@ -531,6 +543,10 @@
org.testcontainers
testcontainers
+
+ com.github.dasniko
+ testcontainers-keycloak
+
org.testcontainers
mysql
diff --git a/main-app/main-webapp/src/main/java/gt/app/DataCreator.java b/main-app/main-webapp/src/main/java/gt/app/DataCreator.java
index 45d131d..089f550 100644
--- a/main-app/main-webapp/src/main/java/gt/app/DataCreator.java
+++ b/main-app/main-webapp/src/main/java/gt/app/DataCreator.java
@@ -15,6 +15,7 @@
import org.springframework.stereotype.Component;
import java.io.File;
+import java.util.UUID;
import java.util.stream.Stream;
@Component
@@ -64,21 +65,22 @@ public void initData() {
userAuthority.setName(Constants.ROLE_USER);
authorityService.save(userAuthority);
- String pwd = "$2a$10$UtqWHf0BfCr41Nsy89gj4OCiL36EbTZ8g4o/IvFN2LArruHruiRXO"; // to make it faster //value is 'pass'
+ String systemUserId = "a621ac4c-6172-4103-9050-b27c053b11eb";
- AppUser adminUser = new AppUser(SYSTEM_USER, "System", "Tiwari", "system@email");
- adminUser.setPassword(pwd);
- adminUser.setAuthorities(authorityService.findByNameIn(Constants.ROLE_ADMIN, Constants.ROLE_USER));
+ if (userService.exists(UUID.fromString(systemUserId))) {
+ log.info("DB already initialized !!!");
+ return;
+ }
+
+ //ID and login are linked with the keycloak export json
+ AppUser adminUser = new AppUser(systemUserId, "system", "System", "Tiwari", "system@email");
userService.save(adminUser);
- AppUser user1 = new AppUser(USER1, "Ganesh", "Tiwari", "gt@email");
- user1.setPassword(pwd);
- user1.setAuthorities(authorityService.findByNameIn(Constants.ROLE_USER));
+ AppUser user1 = new AppUser("d1460f56-7f7e-43e1-8396-bddf39dba08f", "user1", "Ganesh", "Tiwari", "user1@email");
userService.save(user1);
- AppUser user2 = new AppUser(USER2, "Jyoti", "Kattel", "jk@email");
- user2.setPassword(pwd);
- user2.setAuthorities(authorityService.findByNameIn(Constants.ROLE_USER));
+
+ AppUser user2 = new AppUser("fa6820a5-cf39-4cbf-9e50-89cc832bebee", "user2", "Jyoti", "Kattel", "user2@email");
userService.save(user2);
createArticle(adminUser, "Admin's First Article", "Content1 Admin");
diff --git a/main-app/main-webapp/src/main/java/gt/app/api/EmailClient.java b/main-app/main-webapp/src/main/java/gt/app/api/EmailClient.java
index 228f198..6be0c04 100644
--- a/main-app/main-webapp/src/main/java/gt/app/api/EmailClient.java
+++ b/main-app/main-webapp/src/main/java/gt/app/api/EmailClient.java
@@ -9,6 +9,7 @@
import org.springframework.http.ResponseEntity;
@FeignClient(name = "email-service", url = "${feign-clients.email-service.url}", fallback = EmailClient.EmailClientFallback.class)
+//doesn't use auth token
public interface EmailClient extends EmailService {
@Slf4j
class EmailClientFallback implements EmailClient {
diff --git a/main-app/main-webapp/src/main/java/gt/app/api/InternalKeycloakAuthConfig.java b/main-app/main-webapp/src/main/java/gt/app/api/InternalKeycloakAuthConfig.java
new file mode 100644
index 0000000..a70007c
--- /dev/null
+++ b/main-app/main-webapp/src/main/java/gt/app/api/InternalKeycloakAuthConfig.java
@@ -0,0 +1,39 @@
+package gt.app.api;
+
+import feign.RequestInterceptor;
+import gt.app.config.security.SecurityUtils;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.annotation.Bean;
+import org.springframework.security.oauth2.core.oidc.OidcIdToken;
+
+import java.util.Optional;
+
+@Slf4j
+class InternalKeycloakAuthConfig {
+
+ private static final String AUTHORIZATION_HEADER = "Authorization";
+ private static final String BEARER = "Bearer";
+
+
+ @Bean
+ public RequestInterceptor bearerAuthRequestInterceptor() {
+ return template -> {
+ //NOTE: pass the tokens only to the app that expects it
+
+ Optional idTokenOpt = SecurityUtils.getCurrentUserJWT();
+ if (idTokenOpt.isEmpty()) {
+ return;
+ }
+
+ /*
+ //get new token if its about to expire
+ if (idTokenOpt.get().getExpiresAt().isAfter(Instant.now().plus(20, ChronoUnit.SECONDS))) {
+ //TODO: refresh token
+ }
+ */
+ log.info("Calling {} with token", template.url());
+ template.header(AUTHORIZATION_HEADER, String.format("%s %s", BEARER, idTokenOpt.get().getTokenValue()));
+ };
+ }
+
+}
diff --git a/main-app/main-webapp/src/main/java/gt/app/api/ReportClient.java b/main-app/main-webapp/src/main/java/gt/app/api/ReportClient.java
new file mode 100644
index 0000000..486df19
--- /dev/null
+++ b/main-app/main-webapp/src/main/java/gt/app/api/ReportClient.java
@@ -0,0 +1,27 @@
+package gt.app.api;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.cloud.openfeign.FeignClient;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.PostMapping;
+
+@FeignClient(name = "report-service", url = "${feign-clients.report-service.url}", fallback = ReportClient.ReportClientFallback.class, configuration = InternalKeycloakAuthConfig.class)
+public interface ReportClient {
+ @PostMapping(value = "/to-review", produces = MediaType.APPLICATION_JSON_VALUE)
+ FlagCount getFlaggedForReviewCount();
+
+ @Slf4j
+ class ReportClientFallback implements ReportClient {
+
+
+ @Override
+ public FlagCount getFlaggedForReviewCount() {
+ return new FlagCount(-100);
+ }
+ }
+ record FlagCount(int value) {
+ }
+}
+
+
+
diff --git a/main-app/main-webapp/src/main/java/gt/app/config/AppProperties.java b/main-app/main-webapp/src/main/java/gt/app/config/AppProperties.java
index ca932db..1fed24d 100644
--- a/main-app/main-webapp/src/main/java/gt/app/config/AppProperties.java
+++ b/main-app/main-webapp/src/main/java/gt/app/config/AppProperties.java
@@ -9,6 +9,8 @@ public class AppProperties {
final FileStorage fileStorage = new FileStorage();
final JmsProps jms = new JmsProps();
+ final Web web = new Web();
+
final Email email = new Email();
@Data
@@ -22,6 +24,14 @@ public static class JmsProps {
String contentCheckerCallBackResponseQueue;
}
+ @Data
+ public static class Web {
+ /**
+ * the registered host.. without trailing /
+ */
+ String baseUrl;
+ }
+
@Data
public static class Email {
String authorNotificationsFromEmail = "no-reply@system";
diff --git a/main-app/main-webapp/src/main/java/gt/app/config/DockerContainerConfig.java b/main-app/main-webapp/src/main/java/gt/app/config/DockerContainerConfig.java
index 3c46589..4249ee8 100644
--- a/main-app/main-webapp/src/main/java/gt/app/config/DockerContainerConfig.java
+++ b/main-app/main-webapp/src/main/java/gt/app/config/DockerContainerConfig.java
@@ -1,5 +1,6 @@
package gt.app.config;
+import dasniko.testcontainers.keycloak.KeycloakContainer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
@@ -39,6 +40,11 @@ class DockerContainerConfig {
activeMQ.withExposedPorts(61616);
activeMQ.start(); //using default ports
+ var kc = new KeycloakContainer("quay.io/keycloak/keycloak:23.0.3").withRealmImportFile("keycloak/realm-export.json");
+ kc.start();
+
+ setProperty("KEYCLOAK_PORT", Integer.toString(kc.getHttpPort()));
+
setProperty("ACTIVEMQ_ARTEMIS_HOST", activeMQ.getHost());
setProperty("ACTIVEMQ_ARTEMIS_PORT", Integer.toString(activeMQ.getMappedPort(61616)));
setProperty("ACTIVEMQ_ARTEMIS_USER", userPwd);
diff --git a/main-app/main-webapp/src/main/java/gt/app/config/security/AppUserDetails.java b/main-app/main-webapp/src/main/java/gt/app/config/security/AppUserDetails.java
index dd346a3..e85ab9f 100644
--- a/main-app/main-webapp/src/main/java/gt/app/config/security/AppUserDetails.java
+++ b/main-app/main-webapp/src/main/java/gt/app/config/security/AppUserDetails.java
@@ -1,68 +1,68 @@
-package gt.app.config.security;
-
-import com.fasterxml.jackson.annotation.JsonIgnore;
-import gt.app.config.Constants;
-import gt.app.domain.Authority;
-import lombok.Getter;
-import org.springframework.security.core.GrantedAuthority;
-import org.springframework.security.core.userdetails.User;
-
-import java.util.Collection;
-import java.util.Objects;
-import java.util.stream.Collectors;
-
-@Getter
-public class AppUserDetails extends User {
-
- private final Long id;
- private final String firstName;
-
- private final String lastName;
- private final String email;
-
- public AppUserDetails(Long id, String userName, String email, String password, String firstName, String lastName, Collection authorities,
- boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked) {
-
- super(userName, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities);
-
- this.id = id;
- this.firstName = firstName;
- this.lastName = lastName;
- this.email = email;
- }
-
- @JsonIgnore
- public boolean isUser() {
- return getGrantedAuthorities().contains(Constants.ROLE_USER);
- }
-
- @JsonIgnore
- public boolean isSystemAdmin() {
- return getGrantedAuthorities().contains(Constants.ROLE_ADMIN);
- }
-
- public Collection getGrantedAuthorities() {
- Collection authorities = getAuthorities();
- return authorities.stream().map(GrantedAuthority::getAuthority).collect(Collectors.toSet());
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
- if (!super.equals(o)) {
- return false;
- }
- AppUserDetails that = (AppUserDetails) o;
- return id.equals(that.id);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(super.hashCode(), id);
- }
-}
+//package gt.app.config.security;
+//
+//import com.fasterxml.jackson.annotation.JsonIgnore;
+//import gt.app.config.Constants;
+//import gt.app.domain.Authority;
+//import lombok.Getter;
+//import org.springframework.security.core.GrantedAuthority;
+//import org.springframework.security.core.userdetails.User;
+//
+//import java.util.Collection;
+//import java.util.Objects;
+//import java.util.stream.Collectors;
+//
+//@Getter
+//public class AppUserDetails extends User {
+//
+// private final Long id;
+// private final String firstName;
+//
+// private final String lastName;
+// private final String email;
+//
+// public AppUserDetails(Long id, String userName, String email, String password, String firstName, String lastName, Collection authorities,
+// boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked) {
+//
+// super(userName, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities);
+//
+// this.id = id;
+// this.firstName = firstName;
+// this.lastName = lastName;
+// this.email = email;
+// }
+//
+// @JsonIgnore
+// public boolean isUser() {
+// return getGrantedAuthorities().contains(Constants.ROLE_USER);
+// }
+//
+// @JsonIgnore
+// public boolean isSystemAdmin() {
+// return getGrantedAuthorities().contains(Constants.ROLE_ADMIN);
+// }
+//
+// public Collection getGrantedAuthorities() {
+// Collection authorities = getAuthorities();
+// return authorities.stream().map(GrantedAuthority::getAuthority).collect(Collectors.toSet());
+// }
+//
+// @Override
+// public boolean equals(Object o) {
+// if (this == o) {
+// return true;
+// }
+// if (o == null || getClass() != o.getClass()) {
+// return false;
+// }
+// if (!super.equals(o)) {
+// return false;
+// }
+// AppUserDetails that = (AppUserDetails) o;
+// return id.equals(that.id);
+// }
+//
+// @Override
+// public int hashCode() {
+// return Objects.hash(super.hashCode(), id);
+// }
+//}
diff --git a/main-app/main-webapp/src/main/java/gt/app/config/security/CurrentUser.java b/main-app/main-webapp/src/main/java/gt/app/config/security/CurrentUser.java
new file mode 100644
index 0000000..f867334
--- /dev/null
+++ b/main-app/main-webapp/src/main/java/gt/app/config/security/CurrentUser.java
@@ -0,0 +1,13 @@
+package gt.app.config.security;
+
+
+import org.springframework.security.core.annotation.AuthenticationPrincipal;
+
+import java.lang.annotation.*;
+
+@Target({ElementType.PARAMETER, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@AuthenticationPrincipal(errorOnInvalidType = true, expression = "T(gt.app.config.security.SecurityUtils).mapAuthenticationPrincipalToCurrentUser(#this)")
+public @interface CurrentUser {
+}
diff --git a/main-app/main-webapp/src/main/java/gt/app/config/security/CurrentUserToken.java b/main-app/main-webapp/src/main/java/gt/app/config/security/CurrentUserToken.java
new file mode 100644
index 0000000..c52bfef
--- /dev/null
+++ b/main-app/main-webapp/src/main/java/gt/app/config/security/CurrentUserToken.java
@@ -0,0 +1,90 @@
+package gt.app.config.security;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import gt.app.config.Constants;
+import lombok.Getter;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.oauth2.core.oidc.OidcIdToken;
+import org.springframework.security.oauth2.core.oidc.OidcUserInfo;
+import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser;
+
+import java.io.Serial;
+import java.util.Collection;
+import java.util.Objects;
+import java.util.UUID;
+import java.util.stream.Collectors;
+
+@Getter
+public class CurrentUserToken extends DefaultOidcUser {
+
+ @Serial
+ private static final long serialVersionUID = 332175240400944267L;
+ private final Collection authorities;
+ private final UUID userId;
+ private final String username;
+ private final String email;
+
+ public CurrentUserToken(Collection extends GrantedAuthority> authorities, OidcIdToken idToken, OidcUserInfo userInfo) {
+ super(authorities, idToken, userInfo);
+ this.authorities = SecurityUtils.extractAuthorityFromClaims(userInfo.getClaims());
+ this.userId = UUID.fromString(getName());
+ this.username = getAttribute("preferred_username");
+ this.email = getAttribute("email");
+ }
+
+ public CurrentUserToken(Collection authorities, String username) {
+ super(authorities, OidcIdToken.withTokenValue("DUMMY-TEST").claim("DUMMY", "DUMMY").claim("sub", username).build());
+ this.authorities = authorities;
+ this.userId = null;
+ this.username = username;
+ this.email = null;
+ }
+
+ @JsonIgnore
+ public boolean isUser() {
+ return getGrantedAuthorities().contains(Constants.ROLE_USER);
+ }
+
+ @JsonIgnore
+ public boolean isAdmin() {
+ return getGrantedAuthorities().contains(Constants.ROLE_ADMIN);
+ }
+
+ @JsonIgnore
+ public Collection getGrantedAuthorities() {
+ Collection authorities = getAuthorities();
+ return authorities.stream().map(GrantedAuthority::getAuthority).collect(Collectors.toSet());
+ }
+
+ @JsonIgnore
+ public Collection getAuthorities() {
+ return authorities;
+ }
+
+ public UserToken getUserToken() {
+ return new UserToken(getUsername(), getIdToken().getTokenValue(), getGrantedAuthorities());
+ }
+
+ public record UserToken(String username, String token, Collection roles) {
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ if (!super.equals(o)) {
+ return false;
+ }
+ CurrentUserToken that = (CurrentUserToken) o;
+ return Objects.equals(userId, that.userId);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(super.hashCode(), userId);
+ }
+}
diff --git a/main-app/main-webapp/src/main/java/gt/app/config/security/SecurityAuditorResolver.java b/main-app/main-webapp/src/main/java/gt/app/config/security/SecurityAuditorResolver.java
index 1f4c5d7..e546de1 100644
--- a/main-app/main-webapp/src/main/java/gt/app/config/security/SecurityAuditorResolver.java
+++ b/main-app/main-webapp/src/main/java/gt/app/config/security/SecurityAuditorResolver.java
@@ -1,23 +1,28 @@
package gt.app.config.security;
import gt.app.domain.AppUser;
-import gt.app.modules.user.UserService;
+import jakarta.persistence.EntityManager;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.AuditorAware;
import org.springframework.stereotype.Component;
import java.util.Optional;
+import java.util.UUID;
@Component
@RequiredArgsConstructor
class SecurityAuditorResolver implements AuditorAware {
- private final UserService userService;
+ private final EntityManager entityManager;
@Override
public Optional getCurrentAuditor() {
- Optional userLogin = SecurityUtils.getCurrentUserId();
- return userLogin.map(userService::getReference);
+ UUID userId = SecurityUtils.getCurrentUserId();
+ if (userId == null) {
+ return Optional.empty();
+ }
+
+ return Optional.ofNullable(entityManager.getReference(AppUser.class, userId));
}
}
diff --git a/main-app/main-webapp/src/main/java/gt/app/config/security/SecurityConfig.java b/main-app/main-webapp/src/main/java/gt/app/config/security/SecurityConfig.java
index 453f3bf..0e40c71 100644
--- a/main-app/main-webapp/src/main/java/gt/app/config/security/SecurityConfig.java
+++ b/main-app/main-webapp/src/main/java/gt/app/config/security/SecurityConfig.java
@@ -2,6 +2,7 @@
import gt.app.config.Constants;
import lombok.RequiredArgsConstructor;
+import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
@@ -10,10 +11,17 @@
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer;
-import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
-import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
+import org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority;
+import org.springframework.security.oauth2.jwt.JwtDecoder;
+import org.springframework.security.oauth2.jwt.JwtDecoders;
import org.springframework.security.web.SecurityFilterChain;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static org.springframework.security.config.Customizer.withDefaults;
+
@EnableWebSecurity
@EnableMethodSecurity(securedEnabled = true)
@Configuration
@@ -46,22 +54,28 @@ protected SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
.requestMatchers(AUTH_WHITELIST).permitAll()
.requestMatchers("/admin/**").hasAuthority(Constants.ROLE_ADMIN)
.requestMatchers("/user/**").hasAuthority(Constants.ROLE_USER)
+ .requestMatchers("/auth/login").permitAll()
.requestMatchers("/api/**").authenticated()//individual api will be secured differently
.anyRequest().authenticated()) //this one will catch the rest patterns
.csrf(AbstractHttpConfigurer::disable)
- .formLogin(f -> f
- .loginProcessingUrl("/auth/login")
- .permitAll())
- .logout(l -> l
- .logoutUrl("/auth/logout")
- .logoutSuccessUrl("/?logout")
- .permitAll());
+ .oauth2Login(withDefaults())
+ .oauth2ResourceServer(o2 -> o2.jwt(withDefaults()))
+ .oauth2Client(withDefaults());
return http.build();
}
@Bean
- public PasswordEncoder passwordEncoder() {
- return new BCryptPasswordEncoder();
+ JwtDecoder jwtDecoder(@Value("${spring.security.oauth2.client.provider.oidc.issuer-uri}") String issuerUri) {
+ return JwtDecoders.fromOidcIssuerLocation(issuerUri);
+ }
+
+ @Bean
+ public GrantedAuthoritiesMapper userAuthoritiesMapper() {
+ return authorities -> authorities.stream().filter(a -> a instanceof OidcUserAuthority)
+ .map(a -> (OidcUserAuthority) a)
+ .map(a -> SecurityUtils.extractAuthorityFromClaims(a.getUserInfo().getClaims()))
+ .flatMap(List::stream)
+ .collect(Collectors.toSet());
}
}
diff --git a/main-app/main-webapp/src/main/java/gt/app/config/security/SecurityUtils.java b/main-app/main-webapp/src/main/java/gt/app/config/security/SecurityUtils.java
index b02e5c5..d080ada 100644
--- a/main-app/main-webapp/src/main/java/gt/app/config/security/SecurityUtils.java
+++ b/main-app/main-webapp/src/main/java/gt/app/config/security/SecurityUtils.java
@@ -1,32 +1,73 @@
package gt.app.config.security;
+import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.core.Authentication;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
-import org.springframework.security.core.userdetails.User;
+import org.springframework.security.oauth2.core.oidc.OidcIdToken;
+import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser;
-import java.util.Optional;
+import java.util.*;
+import java.util.stream.Collectors;
public final class SecurityUtils {
- public static Optional getCurrentUserId() {
+ private SecurityUtils() {
+ }
+
+ public static UUID getCurrentUserId() {
+
+ CurrentUserToken user = getCurrentUserDetails();
+ if (user == null) {
+ return null;
+ }
+
+ return user.getUserId();
+ }
+
+ public static CurrentUserToken getCurrentUserDetails() {
+ SecurityContext ctx = SecurityContextHolder.getContext();
+ Authentication authentication = ctx.getAuthentication();
+ if (authentication == null) {
+ return null;
+ }
- User user = getCurrentUserDetails();
- if (user instanceof AppUserDetails appUserDetails) {
- return Optional.of(appUserDetails.getId());
+ if (authentication.getPrincipal() instanceof DefaultOidcUser defaultOidcUser) {
+ return mapAuthenticationPrincipalToCurrentUser(defaultOidcUser);
}
- return Optional.empty();
+ return null;
+ }
+
+ public static Optional getCurrentUserJWT() {
+ SecurityContext securityContext = SecurityContextHolder.getContext();
+ return Optional
+ .ofNullable(securityContext.getAuthentication())
+ .filter(auth -> auth.getPrincipal() instanceof DefaultOidcUser)
+ .map(auth -> (DefaultOidcUser) auth.getPrincipal())
+ .map(DefaultOidcUser::getIdToken);
}
- public static User getCurrentUserDetails() {
- var authentication = SecurityContextHolder.getContext().getAuthentication();
- return getCurrentUserDetails(authentication);
+ public static boolean isAuthenticated() {
+ Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+ return !(authentication == null || authentication instanceof AnonymousAuthenticationToken);
}
- public static User getCurrentUserDetails(Authentication authentication) {
- User userDetails = null;
- if (authentication != null && authentication.getPrincipal() instanceof User) {
- userDetails = (User) authentication.getPrincipal();
+ public static List mapRolesToGrantedAuthorities(Collection roles) {
+ if (roles == null) {
+ return Collections.emptyList();
}
- return userDetails;
+ return roles.stream()
+ .filter(role -> role.startsWith("ROLE_"))
+ .map(SimpleGrantedAuthority::new).collect(Collectors.toList());
+ }
+
+ public static List extractAuthorityFromClaims(Map claims) {
+ return mapRolesToGrantedAuthorities((ArrayList) claims.get("roles"));
+ }
+
+ public static CurrentUserToken mapAuthenticationPrincipalToCurrentUser(DefaultOidcUser oidcUser) {
+ return new CurrentUserToken(oidcUser.getAuthorities(), oidcUser.getIdToken(), oidcUser.getUserInfo());
}
}
diff --git a/main-app/main-webapp/src/main/java/gt/app/modules/article/ArticlePreviewDto.java b/main-app/main-webapp/src/main/java/gt/app/modules/article/ArticlePreviewDto.java
index d63204d..76c3484 100644
--- a/main-app/main-webapp/src/main/java/gt/app/modules/article/ArticlePreviewDto.java
+++ b/main-app/main-webapp/src/main/java/gt/app/modules/article/ArticlePreviewDto.java
@@ -20,7 +20,7 @@ public class ArticlePreviewDto {
private String content;
- private Long userId;
+ private UUID userId;
private String username;
private Instant createdDate;
diff --git a/main-app/main-webapp/src/main/java/gt/app/modules/article/ArticleReadDto.java b/main-app/main-webapp/src/main/java/gt/app/modules/article/ArticleReadDto.java
index 63afdd7..196f6ad 100644
--- a/main-app/main-webapp/src/main/java/gt/app/modules/article/ArticleReadDto.java
+++ b/main-app/main-webapp/src/main/java/gt/app/modules/article/ArticleReadDto.java
@@ -21,7 +21,7 @@ public class ArticleReadDto {
private String content;
- private Long userId;
+ private UUID userId;
private String username;
private Instant createdDate;
@@ -44,7 +44,7 @@ public static class CommentDto {
Long id;
String content;
- Long userId;
+ UUID userId;
String username;
Instant createdDate;
diff --git a/main-app/main-webapp/src/main/java/gt/app/modules/article/ArticleRepository.java b/main-app/main-webapp/src/main/java/gt/app/modules/article/ArticleRepository.java
index 5d20ed3..32740af 100644
--- a/main-app/main-webapp/src/main/java/gt/app/modules/article/ArticleRepository.java
+++ b/main-app/main-webapp/src/main/java/gt/app/modules/article/ArticleRepository.java
@@ -13,6 +13,7 @@
import org.springframework.data.repository.query.Param;
import java.util.Optional;
+import java.util.UUID;
public interface ArticleRepository extends AbstractRepository, ArticleRepositoryCustom {
@@ -32,10 +33,10 @@ public interface ArticleRepository extends AbstractRepository, ArticleR
Optional findOneWithUserById(Long id);
@EntityGraph(attributePaths = {"createdByUser", "attachedFiles"})
- Page findWithFilesAndUserByCreatedByUser_IdAndStatusOrderByCreatedDateDesc(Long userId, ArticleStatus status, Pageable pageable);
+ Page findWithFilesAndUserByCreatedByUser_IdAndStatusOrderByCreatedDateDesc(UUID userId, ArticleStatus status, Pageable pageable);
@Query("select n.createdByUser.id from Article n where n.id=:id ")
- Long findCreatedByUserIdById(@Param("id") Long id);
+ UUID findCreatedByUserIdById(@Param("id") Long id);
@EntityGraph(attributePaths = {"createdByUser", "lastModifiedByUser"})
Optional findWithModifiedUserByIdAndStatus(Long id, ArticleStatus flagged);
@@ -44,6 +45,7 @@ public interface ArticleRepository extends AbstractRepository, ArticleR
@Caching(
evict = {
@CacheEvict(cacheNames = {"articleForReview", "articleRead"}, key = "#result.id"),
+ //FIXME: better cache eviction - evict only if the article being updated was cached
@CacheEvict(cacheNames = {"previewForPublicHomePage", "previewAllWithFilesByUser", "getAllToReview"}, allEntries = true)
}
)
@@ -53,6 +55,7 @@ public interface ArticleRepository extends AbstractRepository, ArticleR
@Caching(
evict = {
@CacheEvict(cacheNames = {"articleForReview", "articleRead"}, key = "#id"),
+ //FIXME: better cache eviction - evict only if the article being deleted was cached
@CacheEvict(cacheNames = {"previewForPublicHomePage", "previewAllWithFilesByUser", "getAllToReview"}, allEntries = true)
}
)
diff --git a/main-app/main-webapp/src/main/java/gt/app/modules/article/ArticleService.java b/main-app/main-webapp/src/main/java/gt/app/modules/article/ArticleService.java
index f70efa5..8bb03a0 100644
--- a/main-app/main-webapp/src/main/java/gt/app/modules/article/ArticleService.java
+++ b/main-app/main-webapp/src/main/java/gt/app/modules/article/ArticleService.java
@@ -21,6 +21,7 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
+import java.util.UUID;
@Service
@Slf4j
@@ -113,7 +114,7 @@ public Page previewForPublicHomePage(Pageable pageable) {
}
@Cacheable(cacheNames = "previewAllWithFilesByUser")
- public Page previewAllWithFilesByUser(Pageable pageable, Long userId) {
+ public Page previewAllWithFilesByUser(Pageable pageable, UUID userId) {
return articleRepository.findWithFilesAndUserByCreatedByUser_IdAndStatusOrderByCreatedDateDesc(userId, ArticleStatus.PUBLISHED, pageable)
.map(ArticleMapper.INSTANCE::mapForPreviewListing);
}
@@ -137,7 +138,7 @@ public void delete(Long id) {
}
@Cacheable(cacheNames = {"article-findCreatedByUserIdById"})
- public Long findCreatedByUserIdById(Long articleId) {
+ public UUID findCreatedByUserIdById(Long articleId) {
return articleRepository.findCreatedByUserIdById(articleId);
}
diff --git a/main-app/main-webapp/src/main/java/gt/app/modules/user/AppPermissionEvaluatorService.java b/main-app/main-webapp/src/main/java/gt/app/modules/user/AppPermissionEvaluatorService.java
index 015a0b2..538dc4c 100644
--- a/main-app/main-webapp/src/main/java/gt/app/modules/user/AppPermissionEvaluatorService.java
+++ b/main-app/main-webapp/src/main/java/gt/app/modules/user/AppPermissionEvaluatorService.java
@@ -1,6 +1,6 @@
package gt.app.modules.user;
-import gt.app.config.security.AppUserDetails;
+import gt.app.config.security.CurrentUserToken;
import gt.app.config.security.SecurityUtils;
import gt.app.domain.BaseEntity;
import gt.app.exception.OperationNotAllowedException;
@@ -8,7 +8,6 @@
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.core.Authentication;
-import org.springframework.security.core.userdetails.User;
import org.springframework.stereotype.Service;
import java.io.Serializable;
@@ -45,14 +44,15 @@ public boolean hasPermission(Authentication auth, Serializable targetId, String
}
public boolean hasAccess(Long id, String targetEntity) {
- User curUser = SecurityUtils.getCurrentUserDetails();
+ CurrentUserToken curUserOpt = SecurityUtils.getCurrentUserDetails();
- if (!(curUser instanceof AppUserDetails appUser)) {
- throw new OperationNotAllowedException("Current SecurityContext doesn't have AppUserDetails ");
+ if (curUserOpt == null) {
+ return false;
}
- return userAuthorityService.hasAccess(appUser, id, targetEntity);
+ return userAuthorityService.hasAccess(curUserOpt, id, targetEntity);
}
+
}
diff --git a/main-app/main-webapp/src/main/java/gt/app/modules/user/AppUserDetailsService.java b/main-app/main-webapp/src/main/java/gt/app/modules/user/AppUserDetailsService.java
index e05807b..6cc64ce 100644
--- a/main-app/main-webapp/src/main/java/gt/app/modules/user/AppUserDetailsService.java
+++ b/main-app/main-webapp/src/main/java/gt/app/modules/user/AppUserDetailsService.java
@@ -1,34 +1,34 @@
-package gt.app.modules.user;
-
-import gt.app.config.security.AppUserDetails;
-import gt.app.domain.AppUser;
-import lombok.AllArgsConstructor;
-import org.springframework.security.core.userdetails.UsernameNotFoundException;
-import org.springframework.stereotype.Service;
-import org.springframework.transaction.annotation.Transactional;
-
-import java.util.Optional;
-
-@Service
-@AllArgsConstructor
-public class AppUserDetailsService implements org.springframework.security.core.userdetails.UserDetailsService {
-
- private final UserRepository userRepository;
-
- @Override
- public AppUserDetails loadUserByUsername(String username) {
- Optional userFromDatabase = userRepository.findOneWithAuthoritiesByUsername(username);
-
- return userFromDatabase
- .map(this::getCustomUserDetails)
- .orElseThrow(() -> new UsernameNotFoundException(" User with login:" + username + " was not found in the " + " database "));
- }
-
- @Transactional(readOnly = true)
- public AppUserDetails getCustomUserDetails(AppUser user) {
-
- return new AppUserDetails(user.getId(), user.getUsername(), user.getEmail(), user.getPassword(), user.getFirstName(), user.getLastName(), user.getAuthorities(),
- user.isEnabled(), user.isAccountNonExpired(), user.isCredentialsNonExpired(), user.isAccountNonLocked());
- }
-
-}
+//package gt.app.modules.user;
+//
+//import gt.app.config.security.AppUserDetails;
+//import gt.app.domain.AppUser;
+//import lombok.AllArgsConstructor;
+//import org.springframework.security.core.userdetails.UsernameNotFoundException;
+//import org.springframework.stereotype.Service;
+//import org.springframework.transaction.annotation.Transactional;
+//
+//import java.util.Optional;
+//
+//@Service
+//@AllArgsConstructor
+//public class AppUserDetailsService implements org.springframework.security.core.userdetails.UserDetailsService {
+//
+// private final UserRepository userRepository;
+//
+// @Override
+// public AppUserDetails loadUserByUsername(String username) {
+// Optional userFromDatabase = userRepository.findOneWithAuthoritiesByUsername(username);
+//
+// return userFromDatabase
+// .map(this::getCustomUserDetails)
+// .orElseThrow(() -> new UsernameNotFoundException(" User with login:" + username + " was not found in the " + " database "));
+// }
+//
+// @Transactional(readOnly = true)
+// public AppUserDetails getCustomUserDetails(AppUser user) {
+//
+// return new AppUserDetails(user.getId(), user.getUsername(), user.getEmail(), user.getPassword(), user.getFirstName(), user.getLastName(), user.getAuthorities(),
+// user.isEnabled(), user.isAccountNonExpired(), user.isCredentialsNonExpired(), user.isAccountNonLocked());
+// }
+//
+//}
diff --git a/main-app/main-webapp/src/main/java/gt/app/modules/user/LiteUserRepository.java b/main-app/main-webapp/src/main/java/gt/app/modules/user/LiteUserRepository.java
deleted file mode 100644
index 33a081e..0000000
--- a/main-app/main-webapp/src/main/java/gt/app/modules/user/LiteUserRepository.java
+++ /dev/null
@@ -1,14 +0,0 @@
-package gt.app.modules.user;
-
-import gt.app.domain.LiteUser;
-import org.springframework.cache.annotation.Cacheable;
-import org.springframework.data.jpa.repository.JpaRepository;
-import org.springframework.transaction.annotation.Transactional;
-
-import java.util.Optional;
-
-interface LiteUserRepository extends JpaRepository {
- @Transactional(readOnly = true)
- @Cacheable(value = "userByUsername", unless = "#result == null")
- Optional findOneByUsername(String username);
-}
diff --git a/main-app/main-webapp/src/main/java/gt/app/modules/user/UserAuthorityService.java b/main-app/main-webapp/src/main/java/gt/app/modules/user/UserAuthorityService.java
index 5ec64ef..6a7435f 100644
--- a/main-app/main-webapp/src/main/java/gt/app/modules/user/UserAuthorityService.java
+++ b/main-app/main-webapp/src/main/java/gt/app/modules/user/UserAuthorityService.java
@@ -1,6 +1,6 @@
package gt.app.modules.user;
-import gt.app.config.security.AppUserDetails;
+import gt.app.config.security.CurrentUserToken;
import gt.app.domain.Article;
import gt.app.modules.article.ArticleService;
import lombok.RequiredArgsConstructor;
@@ -8,6 +8,8 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
+import java.util.UUID;
+
@Service("appSecurity")
@RequiredArgsConstructor
@Slf4j
@@ -16,18 +18,18 @@ public class UserAuthorityService {
private final ArticleService articleService;
- public boolean hasAccess(AppUserDetails curUser, Long id, String entity) {
+ public boolean hasAccess(CurrentUserToken curUser, Long id, String entity) {
- if (curUser.isSystemAdmin()) {
+ if (curUser.isAdmin()) {
return true;
}
if (Article.class.getSimpleName().equalsIgnoreCase(entity)) {
- Long createdById = articleService.findCreatedByUserIdById(id);
+ UUID createdById = articleService.findCreatedByUserIdById(id);
- return createdById.equals(curUser.getId());
+ return createdById.equals(curUser.getUserId());
}
diff --git a/main-app/main-webapp/src/main/java/gt/app/modules/user/UserRepository.java b/main-app/main-webapp/src/main/java/gt/app/modules/user/UserRepository.java
index 9111874..b44f993 100644
--- a/main-app/main-webapp/src/main/java/gt/app/modules/user/UserRepository.java
+++ b/main-app/main-webapp/src/main/java/gt/app/modules/user/UserRepository.java
@@ -7,12 +7,13 @@
import org.springframework.transaction.annotation.Transactional;
import java.util.Optional;
+import java.util.UUID;
-interface UserRepository extends JpaRepository {
+interface UserRepository extends JpaRepository {
@Override
@Cacheable("userExistsById")
- boolean existsById(Long id);
+ boolean existsById(UUID id);
@EntityGraph(attributePaths = {"authorities"})
@Transactional(readOnly = true)
diff --git a/main-app/main-webapp/src/main/java/gt/app/modules/user/UserService.java b/main-app/main-webapp/src/main/java/gt/app/modules/user/UserService.java
index 345fe52..acf527c 100644
--- a/main-app/main-webapp/src/main/java/gt/app/modules/user/UserService.java
+++ b/main-app/main-webapp/src/main/java/gt/app/modules/user/UserService.java
@@ -1,22 +1,12 @@
package gt.app.modules.user;
-import gt.api.email.EmailDto;
-import gt.app.api.EmailClient;
-import gt.app.config.Constants;
-import gt.app.config.security.AppUserDetails;
import gt.app.domain.AppUser;
-import gt.app.domain.LiteUser;
-import gt.app.exception.RecordNotFoundException;
-import gt.app.modules.user.dto.PasswordUpdateDTO;
-import gt.app.modules.user.dto.UserProfileUpdateDTO;
-import gt.app.modules.user.dto.UserSignUpDTO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
-import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
-import java.util.Set;
+import java.util.UUID;
@Service
@Transactional
@@ -25,61 +15,20 @@
public class UserService {
private final UserRepository userRepository;
- private final PasswordEncoder passwordEncoder;
- private final AuthorityService authorityService;
- private final EmailClient emailService;
-
- private final LiteUserRepository liteUserRepository;
-
- public void update(UserProfileUpdateDTO toUpdate, AppUserDetails userDetails) {
- LiteUser user = liteUserRepository.findOneByUsername(userDetails.getUsername())
- .orElseThrow(() -> new RecordNotFoundException("User", "login", userDetails.getUsername()));
-
- user.setFirstName(toUpdate.getFirstName());
- user.setLastName(toUpdate.getLastName());
- user.setEmail(toUpdate.getEmail());
-
- liteUserRepository.save(user);
- }
-
- public void updatePassword(PasswordUpdateDTO toUpdate, AppUserDetails userDetails) {
- LiteUser user = liteUserRepository.findOneByUsername(userDetails.getUsername())
- .orElseThrow(() -> new RecordNotFoundException("User", "login", userDetails.getUsername()));
-
- user.setPassword(passwordEncoder.encode(toUpdate.pwdPlainText()));
- liteUserRepository.save(user);
- }
-
- public AppUser create(UserSignUpDTO toCreate) {
-
- //validation is already done
-
- var user = new AppUser(toCreate.getUsername(), toCreate.getFirstName(), toCreate.getLastName(), toCreate.getEmail());
-
- user.setPassword(passwordEncoder.encode(toCreate.getPwdPlaintext()));
-
- user.setAuthorities(authorityService.findByNameIn(Constants.ROLE_USER));
-
- userRepository.save(user);
-
- EmailDto dto = EmailDto.of("system@noteapp", Set.of(user.getEmail()),
- "NoteApp Account Created!",
- "Thanks for signing up.");
-
- emailService.sendEmailWithAttachments(dto);
-
- return user;
- }
public AppUser save(AppUser u) {
return userRepository.save(u);
}
+ public boolean exists(UUID id) {
+ return userRepository.existsById(id);
+ }
+
public boolean existsByUsername(String username) {
return userRepository.existsByUsername(username);
}
- public AppUser getReference(Long id) {
+ public AppUser getReference(UUID id) {
return userRepository.getReferenceById(id);
}
diff --git a/main-app/main-webapp/src/main/java/gt/app/web/mvc/AccountController.java b/main-app/main-webapp/src/main/java/gt/app/web/mvc/AccountController.java
index 79eace3..e54f94c 100644
--- a/main-app/main-webapp/src/main/java/gt/app/web/mvc/AccountController.java
+++ b/main-app/main-webapp/src/main/java/gt/app/web/mvc/AccountController.java
@@ -1,10 +1,10 @@
package gt.app.web.mvc;
-import gt.app.config.security.AppUserDetails;
+import gt.app.config.security.CurrentUser;
+import gt.app.config.security.CurrentUserToken;
import gt.app.modules.user.UserService;
import gt.app.modules.user.UserStat;
import lombok.RequiredArgsConstructor;
-import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@@ -18,7 +18,7 @@ class AccountController {
final UserService userService;
@GetMapping("/account/user/{id}")
- public String getUserSummary(Model model, @AuthenticationPrincipal AppUserDetails loggedInUserDtl, @PathVariable UUID id) {
+ public String getUserSummary(Model model, @CurrentUser CurrentUserToken loggedInUserDtl, @PathVariable UUID id) {
model.addAttribute("name", "FName LastName");
diff --git a/main-app/main-webapp/src/main/java/gt/app/web/mvc/ArticleController.java b/main-app/main-webapp/src/main/java/gt/app/web/mvc/ArticleController.java
index e948b21..9fe44ca 100644
--- a/main-app/main-webapp/src/main/java/gt/app/web/mvc/ArticleController.java
+++ b/main-app/main-webapp/src/main/java/gt/app/web/mvc/ArticleController.java
@@ -1,6 +1,7 @@
package gt.app.web.mvc;
-import gt.app.config.security.AppUserDetails;
+import gt.app.config.security.CurrentUser;
+import gt.app.config.security.CurrentUserToken;
import gt.app.domain.Article;
import gt.app.modules.article.*;
import gt.app.utl.PaginationUtil;
@@ -10,7 +11,6 @@
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.security.access.prepost.PreAuthorize;
-import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@@ -29,15 +29,15 @@ class ArticleController {
final CommentService commentService;
@GetMapping({"/", ""})
- public String userHome(Model model, @AuthenticationPrincipal AppUserDetails u, Pageable pageable) {
- var page = articleService.previewAllWithFilesByUser(PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), Sort.by("createdDate").descending()), u.getId());
+ public String userHome(Model model, @CurrentUser CurrentUserToken u, Pageable pageable) {
+ var page = articleService.previewAllWithFilesByUser(PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), Sort.by("createdDate").descending()), u.getUserId());
model.addAttribute("articles", page);
PaginationUtil.decorateModel(model, page);
return "article";
}
@GetMapping("/new")
- public String startNewArticle(Model model, @AuthenticationPrincipal AppUserDetails loggedInUserDtl) {
+ public String startNewArticle(Model model, @CurrentUser CurrentUserToken loggedInUserDtl) {
model.addAttribute("article", new Article()); //new article box at top
return "article/new-article";
}
diff --git a/main-app/main-webapp/src/main/java/gt/app/web/mvc/AuthController.java b/main-app/main-webapp/src/main/java/gt/app/web/mvc/AuthController.java
new file mode 100644
index 0000000..0609a52
--- /dev/null
+++ b/main-app/main-webapp/src/main/java/gt/app/web/mvc/AuthController.java
@@ -0,0 +1,57 @@
+package gt.app.web.mvc;
+
+import gt.app.config.AppProperties;
+import jakarta.servlet.http.HttpServletRequest;
+import org.springframework.http.HttpHeaders;
+import org.springframework.security.core.annotation.AuthenticationPrincipal;
+import org.springframework.security.oauth2.client.registration.ClientRegistration;
+import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
+import org.springframework.security.oauth2.core.oidc.OidcIdToken;
+import org.springframework.stereotype.Controller;
+import org.springframework.util.StringUtils;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+
+@Controller
+@RequestMapping("/auth")
+public class AuthController {
+
+ private final ClientRegistration registration;
+ private final AppProperties appProperties;
+
+ public AuthController(ClientRegistrationRepository registrations, AppProperties appProperties) {
+ this.registration = registrations.findByRegistrationId("oidc");
+ this.appProperties = appProperties;
+ }
+
+ //FIXME: use keycloak rest api to handle these
+
+// @GetMapping("/change-password")
+// public String changePwd(RedirectAttributes redirectAttributes, HttpServletRequest req, HttpServletResponse resp) {
+// return getKeyCloakAccountUrl(redirectAttributes, req, resp) + "/#/security/signingin";
+// }
+//
+// @GetMapping("/settings")
+// public String settings(RedirectAttributes redirectAttributes, HttpServletRequest req, HttpServletResponse resp) {
+// return getKeyCloakAccountUrl(redirectAttributes, req, resp);
+// }
+
+ @GetMapping("/logout")
+ public String logout(HttpServletRequest request, @AuthenticationPrincipal(expression = "idToken") OidcIdToken idToken) {
+ var logoutUrlSb = new StringBuilder();
+ logoutUrlSb.append(this.registration.getProviderDetails().getConfigurationMetadata().get("end_session_endpoint").toString());
+ String originUrl = request.getHeader(HttpHeaders.ORIGIN);
+ if (!StringUtils.hasText(originUrl)) {
+ originUrl = appProperties.getWeb().getBaseUrl() + "?logout=true";
+ }
+ logoutUrlSb.append("?id_token_hint=").append(idToken.getTokenValue()).append("&post_logout_redirect_uri=").append(originUrl);
+
+ request.getSession().invalidate();
+ return "redirect:" + logoutUrlSb;
+ }
+
+ @GetMapping("/login")
+ public String login() {
+ return "redirect:/oauth2/authorization/oidc"; //this will redirect to login page
+ }
+}
diff --git a/main-app/main-webapp/src/main/java/gt/app/web/mvc/ErrorControllerAdvice.java b/main-app/main-webapp/src/main/java/gt/app/web/mvc/ErrorControllerAdvice.java
index d029a44..2ec517c 100644
--- a/main-app/main-webapp/src/main/java/gt/app/web/mvc/ErrorControllerAdvice.java
+++ b/main-app/main-webapp/src/main/java/gt/app/web/mvc/ErrorControllerAdvice.java
@@ -8,6 +8,7 @@
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
+import org.springframework.web.servlet.NoHandlerFoundException;
@ControllerAdvice
@Slf4j
@@ -16,7 +17,9 @@ class ErrorControllerAdvice {
@ExceptionHandler(Throwable.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public String exception(final Throwable throwable, final Model model) {
- log.error("Exception during execution of application", throwable);
+ if(!(throwable instanceof NoHandlerFoundException)){
+ log.error("Exception during execution of application", throwable);
+ }
String errorMessage = (throwable != null ? throwable.getMessage() : "Unknown error");
model.addAttribute("errorMessage", errorMessage);
diff --git a/main-app/main-webapp/src/main/java/gt/app/web/mvc/IndexController.java b/main-app/main-webapp/src/main/java/gt/app/web/mvc/IndexController.java
index c07e76d..422b713 100644
--- a/main-app/main-webapp/src/main/java/gt/app/web/mvc/IndexController.java
+++ b/main-app/main-webapp/src/main/java/gt/app/web/mvc/IndexController.java
@@ -1,5 +1,6 @@
package gt.app.web.mvc;
+import gt.app.api.ReportClient;
import gt.app.domain.Article;
import gt.app.modules.article.ArticleService;
import gt.app.utl.PaginationUtil;
@@ -18,6 +19,7 @@
class IndexController {
private final ArticleService articleService;
+ private final ReportClient reportClient;
@GetMapping({"/", ""})
public String index(Model model, Pageable pageable) {
@@ -32,6 +34,8 @@ public String index(Model model, Pageable pageable) {
@GetMapping("/admin")
public String adminHome(Model model, Pageable pageable) {
var page = articleService.getAllToReview(PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), Sort.by("createdDate").descending()));
+ var count = reportClient.getFlaggedForReviewCount();
+ model.addAttribute("totalToReview", count.value());
model.addAttribute("articlesToReview", page);
PaginationUtil.decorateModel(model, page);
return "admin/admin-area";
diff --git a/main-app/main-webapp/src/main/java/gt/app/web/mvc/UserController.java b/main-app/main-webapp/src/main/java/gt/app/web/mvc/UserController.java
deleted file mode 100644
index b4ee4c2..0000000
--- a/main-app/main-webapp/src/main/java/gt/app/web/mvc/UserController.java
+++ /dev/null
@@ -1,98 +0,0 @@
-package gt.app.web.mvc;
-
-import gt.app.config.security.AppUserDetails;
-import gt.app.modules.user.PasswordUpdateValidator;
-import gt.app.modules.user.UserService;
-import gt.app.modules.user.UserSignupValidator;
-import gt.app.modules.user.dto.PasswordUpdateDTO;
-import gt.app.modules.user.dto.UserProfileUpdateDTO;
-import gt.app.modules.user.dto.UserSignUpDTO;
-import jakarta.validation.Valid;
-import lombok.RequiredArgsConstructor;
-import org.springframework.security.core.annotation.AuthenticationPrincipal;
-import org.springframework.stereotype.Controller;
-import org.springframework.ui.Model;
-import org.springframework.validation.BindingResult;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.ModelAttribute;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.servlet.mvc.support.RedirectAttributes;
-
-@Controller
-@RequiredArgsConstructor
-class UserController {
-
- private final UserService userService;
- private final UserSignupValidator userSignupValidator;
- private final PasswordUpdateValidator passwordUpdateValidator;
-
- @GetMapping(value = "/signup")
- public String register(Model model) {
- model.addAttribute("user", new UserSignUpDTO());
- return "user/signup";
- }
-
- @PostMapping(value = "/signup")
- public String register(@Valid @ModelAttribute("user") UserSignUpDTO user, BindingResult bindingResult,
- RedirectAttributes redirectAttrs) {
-
- //do custom validation(MVC style) along with the BeanValidation
- userSignupValidator.validate(user, bindingResult);
-
- if (bindingResult.hasErrors()) {
- return "user/signup";
- }
-
- userService.create(user);
-
- redirectAttrs.addFlashAttribute("success", "User " + user.getUsername() + " is created");
-
- return "redirect:/";
- }
-
- @GetMapping(value = "/profile")
- public String updateProfile(Model model, @AuthenticationPrincipal AppUserDetails loggedInUserDtl) {
- model.addAttribute("user", new UserProfileUpdateDTO(loggedInUserDtl.getEmail(), loggedInUserDtl.getFirstName(), loggedInUserDtl.getLastName()));
- return "user/profile";
- }
-
- @PostMapping(value = "/profile")
- public String updateProfile(@Valid @ModelAttribute("user") UserProfileUpdateDTO user, BindingResult bindingResult,
- @AuthenticationPrincipal AppUserDetails loggedInUserDtl, RedirectAttributes redirectAttrs) {
-
- if (bindingResult.hasErrors()) {
- return "user/profile";
- }
-
- userService.update(user, loggedInUserDtl);
-
- redirectAttrs.addFlashAttribute("success", "Profile updated successfully");
-
- return "redirect:/"; //self
- }
-
- @GetMapping(value = "/password")
- public String updatePassword(Model model) {
- model.addAttribute("user", PasswordUpdateDTO.of());
- return "user/password";
- }
-
- @PostMapping(value = "/password")
- public String updatePassword(@Valid @ModelAttribute("user") PasswordUpdateDTO reqDto, BindingResult bindingResult,
- @AuthenticationPrincipal AppUserDetails loggedInUserDtl, RedirectAttributes redirectAttrs) {
-
- //do custom validation along with the BeanValidation
- passwordUpdateValidator.validate(reqDto, bindingResult, loggedInUserDtl);
-
- if (bindingResult.hasErrors()) {
- return "user/password";
- }
-
- userService.updatePassword(reqDto, loggedInUserDtl);
-
- redirectAttrs.addFlashAttribute("success", "Password updated successfully");
-
- return "redirect:/";
- }
-
-}
diff --git a/main-app/main-webapp/src/main/java/gt/app/web/rest/UserResource.java b/main-app/main-webapp/src/main/java/gt/app/web/rest/UserResource.java
index 64bf0f8..4df18a9 100644
--- a/main-app/main-webapp/src/main/java/gt/app/web/rest/UserResource.java
+++ b/main-app/main-webapp/src/main/java/gt/app/web/rest/UserResource.java
@@ -1,5 +1,6 @@
package gt.app.web.rest;
+import gt.app.config.security.CurrentUserToken;
import gt.app.config.security.SecurityUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -7,8 +8,6 @@
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
-import java.util.Optional;
-
@RestController
@RequestMapping("/api")
@RequiredArgsConstructor
@@ -16,7 +15,11 @@
class UserResource {
@GetMapping("/account")
- public Optional getAccount() {
- return SecurityUtils.getCurrentUserId();
+ public CurrentUserToken.UserToken getAccount() {
+ var user = SecurityUtils.getCurrentUserDetails();
+ if (user != null) {
+ user.getUserToken();
+ }
+ return null;
}
}
diff --git a/main-app/main-webapp/src/main/resources/application-dev.yml b/main-app/main-webapp/src/main/resources/application-dev.yml
index 00df16f..e6d1045 100644
--- a/main-app/main-webapp/src/main/resources/application-dev.yml
+++ b/main-app/main-webapp/src/main/resources/application-dev.yml
@@ -41,6 +41,8 @@ spring:
feign-clients:
email-service:
url: http://localhost:8085/ #TODO: use service discovery
+ report-service:
+ url: http://localhost:8086/ #TODO: use service discovery
logging.level:
org.jooq.tools.LoggerListener: DEBUG
diff --git a/main-app/main-webapp/src/main/resources/application.yml b/main-app/main-webapp/src/main/resources/application.yml
index cb36021..463b274 100644
--- a/main-app/main-webapp/src/main/resources/application.yml
+++ b/main-app/main-webapp/src/main/resources/application.yml
@@ -20,7 +20,21 @@ spring:
openfeign:
micrometer:
enabled: true
-
+ security:
+ oauth2:
+ client:
+ # we are using authorization code flow (login within keycloak app web ui)
+ provider:
+ oidc:
+ # use http://localhost:8082/realms/articleapp/.well-known/openid-configuration to get config from keycloak
+ issuer-uri: http://localhost:${KEYCLOAK_PORT:8082}/realms/seedapp
+ registration:
+ oidc:
+ # authorization-grant-type: refresh_token
+ client-id: seed-app-gateway-client
+ client-secret: secret123
+ scope: openid, profile, email, offline_access # last one for refresh tokens
+ # we can have multiple auth providers
server:
port: 8081
@@ -41,7 +55,8 @@ app-properties:
content-checkercallback-response-queue: jms-content-checkercallback-responsequeue
file-storage:
upload-folder: ${java.io.tmpdir}/uploads
-
+ web:
+ base-url: http://localhost:${server.port}
wro4j:
jmx-enabled: false
debug: true
diff --git a/main-app/main-webapp/src/main/resources/keycloak/realm-export.json b/main-app/main-webapp/src/main/resources/keycloak/realm-export.json
new file mode 100644
index 0000000..486cbdf
--- /dev/null
+++ b/main-app/main-webapp/src/main/resources/keycloak/realm-export.json
@@ -0,0 +1,2585 @@
+{
+ "id": "seed-app",
+ "realm": "seedapp",
+ "displayName": "See App IAM",
+ "notBefore": 1580089862,
+ "defaultSignatureAlgorithm": "RS256",
+ "revokeRefreshToken": false,
+ "refreshTokenMaxReuse": 0,
+ "accessTokenLifespan": 300,
+ "accessTokenLifespanForImplicitFlow": 900,
+ "ssoSessionIdleTimeout": 1800,
+ "ssoSessionMaxLifespan": 36000,
+ "ssoSessionIdleTimeoutRememberMe": 0,
+ "ssoSessionMaxLifespanRememberMe": 0,
+ "offlineSessionIdleTimeout": 2592000,
+ "offlineSessionMaxLifespanEnabled": false,
+ "offlineSessionMaxLifespan": 5184000,
+ "clientSessionIdleTimeout": 0,
+ "clientSessionMaxLifespan": 0,
+ "clientOfflineSessionIdleTimeout": 0,
+ "clientOfflineSessionMaxLifespan": 0,
+ "accessCodeLifespan": 60,
+ "accessCodeLifespanUserAction": 300,
+ "accessCodeLifespanLogin": 1800,
+ "actionTokenGeneratedByAdminLifespan": 43200,
+ "actionTokenGeneratedByUserLifespan": 300,
+ "oauth2DeviceCodeLifespan": 600,
+ "oauth2DevicePollingInterval": 5,
+ "enabled": true,
+ "sslRequired": "external",
+ "registrationAllowed": false,
+ "registrationEmailAsUsername": false,
+ "rememberMe": false,
+ "verifyEmail": false,
+ "loginWithEmailAllowed": true,
+ "duplicateEmailsAllowed": false,
+ "resetPasswordAllowed": false,
+ "editUsernameAllowed": false,
+ "bruteForceProtected": false,
+ "permanentLockout": false,
+ "maxFailureWaitSeconds": 900,
+ "minimumQuickLoginWaitSeconds": 60,
+ "waitIncrementSeconds": 60,
+ "quickLoginCheckMilliSeconds": 1000,
+ "maxDeltaTimeSeconds": 43200,
+ "failureFactor": 30,
+ "roles": {
+ "realm": [
+ {
+ "id": "8e986fb5-dafb-43bf-a7c2-7e57572d3d80",
+ "name": "ROLE_ADMIN",
+ "description": "seedapp administrator role",
+ "composite": false,
+ "clientRole": false,
+ "containerId": "seed-app",
+ "attributes": {}
+ },
+ {
+ "id": "e1b19afd-f612-4a79-bdf8-26a99b89b10b",
+ "name": "offline_access",
+ "description": "${role_offline-access}",
+ "composite": false,
+ "clientRole": false,
+ "containerId": "seed-app",
+ "attributes": {}
+ },
+ {
+ "id": "ec5705e1-fc1d-4d21-8364-abd3bd4efcd0",
+ "name": "ROLE_USER",
+ "description": "seedapp user role",
+ "composite": false,
+ "clientRole": false,
+ "containerId": "seed-app",
+ "attributes": {}
+ },
+ {
+ "id": "d2b73e7b-a2d7-40e9-8ebc-2af00454e8aa",
+ "name": "default-roles-seedapp",
+ "description": "${role_default-roles}",
+ "composite": true,
+ "composites": {
+ "realm": [
+ "offline_access",
+ "uma_authorization"
+ ],
+ "client": {
+ "account": [
+ "view-profile",
+ "manage-account"
+ ]
+ }
+ },
+ "clientRole": false,
+ "containerId": "seed-app",
+ "attributes": {}
+ },
+ {
+ "id": "2eec61d0-9581-4dbf-8c7b-f32dc5fac3ce",
+ "name": "uma_authorization",
+ "description": "${role_uma_authorization}",
+ "composite": false,
+ "clientRole": false,
+ "containerId": "seed-app",
+ "attributes": {}
+ }
+ ],
+ "client": {
+ "internal": [],
+ "realm-management": [
+ {
+ "id": "a6249a12-d76c-4514-b137-e3018b243e25",
+ "name": "manage-authorization",
+ "description": "${role_manage-authorization}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "898488c8-e260-41c5-a463-7ceea14d587a",
+ "attributes": {}
+ },
+ {
+ "id": "a28bc401-c5ad-4fab-aef4-42629988c10b",
+ "name": "view-realm",
+ "description": "${role_view-realm}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "898488c8-e260-41c5-a463-7ceea14d587a",
+ "attributes": {}
+ },
+ {
+ "id": "464bca1f-136f-45de-a7fc-b976a185ce7e",
+ "name": "view-users",
+ "description": "${role_view-users}",
+ "composite": true,
+ "composites": {
+ "client": {
+ "realm-management": [
+ "query-users",
+ "query-groups"
+ ]
+ }
+ },
+ "clientRole": true,
+ "containerId": "898488c8-e260-41c5-a463-7ceea14d587a",
+ "attributes": {}
+ },
+ {
+ "id": "98c2fa77-d3c8-4f68-b9f4-b79f87efd4a9",
+ "name": "query-users",
+ "description": "${role_query-users}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "898488c8-e260-41c5-a463-7ceea14d587a",
+ "attributes": {}
+ },
+ {
+ "id": "6b82bfdb-c8de-4274-95b4-a683eb4ead50",
+ "name": "view-identity-providers",
+ "description": "${role_view-identity-providers}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "898488c8-e260-41c5-a463-7ceea14d587a",
+ "attributes": {}
+ },
+ {
+ "id": "3c6b9cfe-80c4-41d5-a5ac-0cadebacfc8d",
+ "name": "manage-identity-providers",
+ "description": "${role_manage-identity-providers}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "898488c8-e260-41c5-a463-7ceea14d587a",
+ "attributes": {}
+ },
+ {
+ "id": "23676fb8-235a-4e54-a0d0-9bed1ccbe2f8",
+ "name": "query-groups",
+ "description": "${role_query-groups}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "898488c8-e260-41c5-a463-7ceea14d587a",
+ "attributes": {}
+ },
+ {
+ "id": "b71fe952-bb06-4e4a-91ef-2d2714f770e1",
+ "name": "impersonation",
+ "description": "${role_impersonation}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "898488c8-e260-41c5-a463-7ceea14d587a",
+ "attributes": {}
+ },
+ {
+ "id": "0813cbd0-c73d-469d-a54d-84a865c302af",
+ "name": "manage-events",
+ "description": "${role_manage-events}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "898488c8-e260-41c5-a463-7ceea14d587a",
+ "attributes": {}
+ },
+ {
+ "id": "c7a4f4c1-9089-458c-a765-f6d22ea94690",
+ "name": "view-authorization",
+ "description": "${role_view-authorization}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "898488c8-e260-41c5-a463-7ceea14d587a",
+ "attributes": {}
+ },
+ {
+ "id": "2e1bc884-e9d3-45d2-909c-2777a78ca8ae",
+ "name": "manage-realm",
+ "description": "${role_manage-realm}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "898488c8-e260-41c5-a463-7ceea14d587a",
+ "attributes": {}
+ },
+ {
+ "id": "0a05451e-7d64-4e87-b585-f1143ce5752e",
+ "name": "query-clients",
+ "description": "${role_query-clients}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "898488c8-e260-41c5-a463-7ceea14d587a",
+ "attributes": {}
+ },
+ {
+ "id": "dfad4d08-6d75-42b6-8699-4886e47bc464",
+ "name": "view-events",
+ "description": "${role_view-events}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "898488c8-e260-41c5-a463-7ceea14d587a",
+ "attributes": {}
+ },
+ {
+ "id": "392ed0a3-f6ad-48a1-b201-648037d2b4bd",
+ "name": "realm-admin",
+ "description": "${role_realm-admin}",
+ "composite": true,
+ "composites": {
+ "client": {
+ "realm-management": [
+ "manage-authorization",
+ "view-realm",
+ "view-users",
+ "query-users",
+ "manage-identity-providers",
+ "view-identity-providers",
+ "query-groups",
+ "impersonation",
+ "manage-events",
+ "query-clients",
+ "manage-realm",
+ "view-authorization",
+ "view-events",
+ "view-clients",
+ "create-client",
+ "manage-clients",
+ "manage-users",
+ "query-realms"
+ ]
+ }
+ },
+ "clientRole": true,
+ "containerId": "898488c8-e260-41c5-a463-7ceea14d587a",
+ "attributes": {}
+ },
+ {
+ "id": "d7efdf61-affb-42a1-bcb0-b2c30d87a39e",
+ "name": "view-clients",
+ "description": "${role_view-clients}",
+ "composite": true,
+ "composites": {
+ "client": {
+ "realm-management": [
+ "query-clients"
+ ]
+ }
+ },
+ "clientRole": true,
+ "containerId": "898488c8-e260-41c5-a463-7ceea14d587a",
+ "attributes": {}
+ },
+ {
+ "id": "14da8e56-5c8b-4764-96da-250449a32fd4",
+ "name": "create-client",
+ "description": "${role_create-client}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "898488c8-e260-41c5-a463-7ceea14d587a",
+ "attributes": {}
+ },
+ {
+ "id": "88e6a9f5-259c-487d-af35-2a98da066816",
+ "name": "manage-clients",
+ "description": "${role_manage-clients}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "898488c8-e260-41c5-a463-7ceea14d587a",
+ "attributes": {}
+ },
+ {
+ "id": "932273a7-c02b-43db-81c5-96a0dc45e454",
+ "name": "manage-users",
+ "description": "${role_manage-users}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "898488c8-e260-41c5-a463-7ceea14d587a",
+ "attributes": {}
+ },
+ {
+ "id": "e3edf335-cec5-4012-a00d-fcac045052e1",
+ "name": "query-realms",
+ "description": "${role_query-realms}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "898488c8-e260-41c5-a463-7ceea14d587a",
+ "attributes": {}
+ }
+ ],
+ "seedapp-control-center": [],
+ "security-admin-console": [],
+ "seed-app-gateway-client": [],
+ "admin-cli": [],
+ "account-console": [],
+ "broker": [
+ {
+ "id": "5b08a930-9f1d-4030-ae75-92c1e4c9352c",
+ "name": "read-token",
+ "description": "${role_read-token}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "88e1225b-f0b9-46ba-8efd-f2c10ce23058",
+ "attributes": {}
+ }
+ ],
+ "account": [
+ {
+ "id": "a88c56b8-6bc9-418a-92bc-7a17e7707f60",
+ "name": "view-profile",
+ "description": "${role_view-profile}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "6cc5a716-0880-47dc-b714-9a4967246b2f",
+ "attributes": {}
+ },
+ {
+ "id": "0cb954ab-987f-482a-b2d7-0d481ba1d532",
+ "name": "view-applications",
+ "description": "${role_view-applications}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "6cc5a716-0880-47dc-b714-9a4967246b2f",
+ "attributes": {}
+ },
+ {
+ "id": "6450156d-7526-48f2-8ea0-bb1e51f9eefa",
+ "name": "manage-account",
+ "description": "${role_manage-account}",
+ "composite": true,
+ "composites": {
+ "client": {
+ "account": [
+ "manage-account-links"
+ ]
+ }
+ },
+ "clientRole": true,
+ "containerId": "6cc5a716-0880-47dc-b714-9a4967246b2f",
+ "attributes": {}
+ },
+ {
+ "id": "e5b2ba76-4c36-4ba1-b210-89a1ac3c6bbe",
+ "name": "view-consent",
+ "description": "${role_view-consent}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "6cc5a716-0880-47dc-b714-9a4967246b2f",
+ "attributes": {}
+ },
+ {
+ "id": "35537940-67a6-4217-881b-1ff98109b374",
+ "name": "manage-consent",
+ "description": "${role_manage-consent}",
+ "composite": true,
+ "composites": {
+ "client": {
+ "account": [
+ "view-consent"
+ ]
+ }
+ },
+ "clientRole": true,
+ "containerId": "6cc5a716-0880-47dc-b714-9a4967246b2f",
+ "attributes": {}
+ },
+ {
+ "id": "5ebf404b-7805-4da2-abb4-9db7d3b36120",
+ "name": "delete-account",
+ "description": "${role_delete-account}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "6cc5a716-0880-47dc-b714-9a4967246b2f",
+ "attributes": {}
+ },
+ {
+ "id": "a7f45fab-19c3-4c48-aca3-85f828ca0fed",
+ "name": "manage-account-links",
+ "description": "${role_manage-account-links}",
+ "composite": false,
+ "clientRole": true,
+ "containerId": "6cc5a716-0880-47dc-b714-9a4967246b2f",
+ "attributes": {}
+ }
+ ]
+ }
+ },
+ "groups": [
+ {
+ "id": "afb0c768-ab0f-454c-a8ea-bc9e70b50248",
+ "name": "Admins",
+ "path": "/Admins",
+ "attributes": {},
+ "realmRoles": [
+ "ROLE_ADMIN"
+ ],
+ "clientRoles": {},
+ "subGroups": []
+ },
+ {
+ "id": "672767bb-4ab0-4d37-93a1-9b6c2416b6b2",
+ "name": "Users",
+ "path": "/Users",
+ "attributes": {},
+ "realmRoles": [
+ "ROLE_USER"
+ ],
+ "clientRoles": {},
+ "subGroups": []
+ }
+ ],
+ "defaultRole": {
+ "id": "d2b73e7b-a2d7-40e9-8ebc-2af00454e8aa",
+ "name": "default-roles-seedapp",
+ "description": "${role_default-roles}",
+ "composite": true,
+ "clientRole": false,
+ "containerId": "seed-app"
+ },
+ "requiredCredentials": [
+ "password"
+ ],
+ "otpPolicyType": "totp",
+ "otpPolicyAlgorithm": "HmacSHA1",
+ "otpPolicyInitialCounter": 0,
+ "otpPolicyDigits": 6,
+ "otpPolicyLookAheadWindow": 1,
+ "otpPolicyPeriod": 30,
+ "otpSupportedApplications": [
+ "FreeOTP",
+ "Google Authenticator"
+ ],
+ "webAuthnPolicyRpEntityName": "keycloak",
+ "webAuthnPolicySignatureAlgorithms": [
+ "ES256"
+ ],
+ "webAuthnPolicyRpId": "",
+ "webAuthnPolicyAttestationConveyancePreference": "not specified",
+ "webAuthnPolicyAuthenticatorAttachment": "not specified",
+ "webAuthnPolicyRequireResidentKey": "not specified",
+ "webAuthnPolicyUserVerificationRequirement": "not specified",
+ "webAuthnPolicyCreateTimeout": 0,
+ "webAuthnPolicyAvoidSameAuthenticatorRegister": false,
+ "webAuthnPolicyAcceptableAaguids": [],
+ "webAuthnPolicyPasswordlessRpEntityName": "keycloak",
+ "webAuthnPolicyPasswordlessSignatureAlgorithms": [
+ "ES256"
+ ],
+ "webAuthnPolicyPasswordlessRpId": "",
+ "webAuthnPolicyPasswordlessAttestationConveyancePreference": "not specified",
+ "webAuthnPolicyPasswordlessAuthenticatorAttachment": "not specified",
+ "webAuthnPolicyPasswordlessRequireResidentKey": "not specified",
+ "webAuthnPolicyPasswordlessUserVerificationRequirement": "not specified",
+ "webAuthnPolicyPasswordlessCreateTimeout": 0,
+ "webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister": false,
+ "webAuthnPolicyPasswordlessAcceptableAaguids": [],
+ "users": [
+ {
+ "id": "eeefadeb-340e-4416-ab0f-68a9da67a0e9",
+ "createdTimestamp": 1580092657614,
+ "username": "gtiwari",
+ "enabled": true,
+ "totp": false,
+ "emailVerified": true,
+ "firstName": "Ganesh",
+ "lastName": "Tiwari",
+ "email": "gtiwari@email",
+ "credentials": [
+ {
+ "id": "4d636339-31c2-47a7-8024-c1e6fdb08462",
+ "type": "password",
+ "createdDate": 1580092657696,
+ "secretData": "{\"value\":\"vgLtDly2/bzpi0DMGn33wrTQrGV2KBV2e2GTiwhaUIT+BJVQ5XwoLRJbFU5ZaJXBmzdLv1/iO/NLmSeuYmiOQQ==\",\"salt\":\"GMaMEZT1aXSLCY1JiA7ivw==\"}",
+ "credentialData": "{\"hashIterations\":27500,\"algorithm\":\"pbkdf2-sha256\"}"
+ }
+ ],
+ "disableableCredentialTypes": [],
+ "requiredActions": [],
+ "realmRoles": [
+ "offline_access",
+ "uma_authorization"
+ ],
+ "clientRoles": {
+ "account": [
+ "view-profile",
+ "manage-account"
+ ]
+ },
+ "notBefore": 0,
+ "groups": [
+ "/Admins",
+ "/Users"
+ ]
+ },
+ {
+ "id": "a621ac4c-6172-4103-9050-b27c053b11eb",
+ "createdTimestamp": 1580005071465,
+ "username": "system",
+ "email": "system@email",
+ "enabled": true,
+ "totp": false,
+ "emailVerified": true,
+ "credentials": [
+ {
+ "id": "85153370-dac3-4c4e-ad0f-d56be0989ac1",
+ "type": "password",
+ "createdDate": 1580005084934,
+ "secretData": "{\"value\":\"UAFe+kqnvwEM0ZXifVch3M/i6e5baljh/D7tgX9+UqlLywqf5ANulu7e6Qy/qYs/6nYFy/DxE88H+BWUlgHS3A==\",\"salt\":\"L6BvFcInxqsOBvS/itv6pQ==\"}",
+ "credentialData": "{\"hashIterations\":27500,\"algorithm\":\"pbkdf2-sha256\"}"
+ }
+ ],
+ "disableableCredentialTypes": [],
+ "requiredActions": [],
+ "realmRoles": [
+ "offline_access",
+ "uma_authorization"
+ ],
+ "clientRoles": {
+ "account": [
+ "view-profile",
+ "manage-account"
+ ]
+ },
+ "notBefore": 0,
+ "groups": [
+ "/Admins",
+ "/Users"
+ ]
+ },
+ {
+ "id": "d1460f56-7f7e-43e1-8396-bddf39dba08f",
+ "createdTimestamp": 1580005095668,
+ "username": "user1",
+ "email": "user1@email",
+ "enabled": true,
+ "totp": false,
+ "emailVerified": true,
+ "firstName": "User 1",
+ "lastName": "Tiwari",
+ "credentials": [
+ {
+ "id": "e9ee782e-ba45-4b26-94e0-0a9125dcd3c5",
+ "type": "password",
+ "createdDate": 1580005125789,
+ "secretData": "{\"value\":\"SJONVU1ktBTDrsDZ8U15g/gYsYvLFVy7IcVFd4IrDtEwau9DqznYdJZp+wOqzTI3VF4YQV2Mih/pVciljgTnHQ==\",\"salt\":\"jxoPcDGtujZ0cO44I75mgA==\"}",
+ "credentialData": "{\"hashIterations\":27500,\"algorithm\":\"pbkdf2-sha256\"}"
+ }
+ ],
+ "disableableCredentialTypes": [],
+ "requiredActions": [],
+ "realmRoles": [
+ "offline_access",
+ "uma_authorization"
+ ],
+ "clientRoles": {
+ "account": [
+ "view-profile",
+ "manage-account"
+ ]
+ },
+ "notBefore": 0,
+ "groups": [
+ "/Users"
+ ]
+ },
+ {
+ "id": "fa6820a5-cf39-4cbf-9e50-89cc832bebee",
+ "createdTimestamp": 1580005143120,
+ "username": "user2",
+ "email": "user2@email",
+ "enabled": true,
+ "totp": false,
+ "emailVerified": true,
+ "firstName": "User 2",
+ "lastName": "Tiwari",
+ "credentials": [
+ {
+ "id": "20f32b3e-1ee2-49f2-8484-0d7dcb555ea8",
+ "type": "password",
+ "createdDate": 1580005153215,
+ "secretData": "{\"value\":\"9j7daptNaNi46sh5GVnKbFesTVvrdYjzYFmS1CM5+TlvElOPeeQjHNMou/a//rTm527taKAkaqxPzmAMd98/SA==\",\"salt\":\"sj7WlqvcbmItkmpeNRDfBA==\"}",
+ "credentialData": "{\"hashIterations\":27500,\"algorithm\":\"pbkdf2-sha256\"}"
+ }
+ ],
+ "disableableCredentialTypes": [],
+ "requiredActions": [],
+ "realmRoles": [
+ "offline_access",
+ "uma_authorization"
+ ],
+ "clientRoles": {
+ "account": [
+ "view-profile",
+ "manage-account"
+ ]
+ },
+ "notBefore": 0,
+ "groups": [
+ "/Users"
+ ]
+ }
+ ],
+ "scopeMappings": [
+ {
+ "clientScope": "offline_access",
+ "roles": [
+ "offline_access"
+ ]
+ }
+ ],
+ "clientScopeMappings": {
+ "account": [
+ {
+ "client": "account-console",
+ "roles": [
+ "manage-account"
+ ]
+ }
+ ]
+ },
+ "clients": [
+ {
+ "id": "6cc5a716-0880-47dc-b714-9a4967246b2f",
+ "clientId": "account",
+ "name": "${client_account}",
+ "rootUrl": "${authBaseUrl}",
+ "baseUrl": "/realms/seedapp/account/",
+ "surrogateAuthRequired": false,
+ "enabled": true,
+ "alwaysDisplayInConsole": false,
+ "clientAuthenticatorType": "client-secret",
+ "secret": "**********",
+ "redirectUris": [
+ "/realms/seedapp/account/*"
+ ],
+ "webOrigins": [],
+ "notBefore": 0,
+ "bearerOnly": false,
+ "consentRequired": false,
+ "standardFlowEnabled": true,
+ "implicitFlowEnabled": false,
+ "directAccessGrantsEnabled": false,
+ "serviceAccountsEnabled": false,
+ "publicClient": false,
+ "frontchannelLogout": false,
+ "protocol": "openid-connect",
+ "attributes": {
+ "post.logout.redirect.uris": "+"
+ },
+ "authenticationFlowBindingOverrides": {},
+ "fullScopeAllowed": false,
+ "nodeReRegistrationTimeout": 0,
+ "defaultClientScopes": [
+ "web-origins",
+ "roles",
+ "profile",
+ "email"
+ ],
+ "optionalClientScopes": [
+ "address",
+ "phone",
+ "offline_access",
+ "microprofile-jwt"
+ ]
+ },
+ {
+ "id": "fb0a4870-06db-4f9d-9d44-baf51a00cc34",
+ "clientId": "account-console",
+ "name": "${client_account-console}",
+ "rootUrl": "${authBaseUrl}",
+ "baseUrl": "/realms/seedapp/account/",
+ "surrogateAuthRequired": false,
+ "enabled": true,
+ "alwaysDisplayInConsole": false,
+ "clientAuthenticatorType": "client-secret",
+ "secret": "**********",
+ "redirectUris": [
+ "/realms/seedapp/account/*"
+ ],
+ "webOrigins": [],
+ "notBefore": 0,
+ "bearerOnly": false,
+ "consentRequired": false,
+ "standardFlowEnabled": true,
+ "implicitFlowEnabled": false,
+ "directAccessGrantsEnabled": false,
+ "serviceAccountsEnabled": false,
+ "publicClient": true,
+ "frontchannelLogout": false,
+ "protocol": "openid-connect",
+ "attributes": {
+ "post.logout.redirect.uris": "+",
+ "pkce.code.challenge.method": "S256"
+ },
+ "authenticationFlowBindingOverrides": {},
+ "fullScopeAllowed": false,
+ "nodeReRegistrationTimeout": 0,
+ "protocolMappers": [
+ {
+ "id": "c5c4ebe5-d009-4f96-b143-1b36d770eafb",
+ "name": "audience resolve",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-audience-resolve-mapper",
+ "consentRequired": false,
+ "config": {}
+ }
+ ],
+ "defaultClientScopes": [
+ "web-origins",
+ "roles",
+ "profile",
+ "email"
+ ],
+ "optionalClientScopes": [
+ "address",
+ "phone",
+ "offline_access",
+ "microprofile-jwt"
+ ]
+ },
+ {
+ "id": "bb166356-838d-445e-94e3-9330ad7ab51b",
+ "clientId": "admin-cli",
+ "name": "${client_admin-cli}",
+ "surrogateAuthRequired": false,
+ "enabled": true,
+ "alwaysDisplayInConsole": false,
+ "clientAuthenticatorType": "client-secret",
+ "secret": "**********",
+ "redirectUris": [],
+ "webOrigins": [],
+ "notBefore": 0,
+ "bearerOnly": false,
+ "consentRequired": false,
+ "standardFlowEnabled": false,
+ "implicitFlowEnabled": false,
+ "directAccessGrantsEnabled": true,
+ "serviceAccountsEnabled": false,
+ "publicClient": true,
+ "frontchannelLogout": false,
+ "protocol": "openid-connect",
+ "attributes": {
+ "post.logout.redirect.uris": "+"
+ },
+ "authenticationFlowBindingOverrides": {},
+ "fullScopeAllowed": false,
+ "nodeReRegistrationTimeout": 0,
+ "defaultClientScopes": [
+ "web-origins",
+ "roles",
+ "profile",
+ "email"
+ ],
+ "optionalClientScopes": [
+ "address",
+ "phone",
+ "offline_access",
+ "microprofile-jwt"
+ ]
+ },
+ {
+ "id": "88e1225b-f0b9-46ba-8efd-f2c10ce23058",
+ "clientId": "broker",
+ "name": "${client_broker}",
+ "surrogateAuthRequired": false,
+ "enabled": true,
+ "alwaysDisplayInConsole": false,
+ "clientAuthenticatorType": "client-secret",
+ "secret": "**********",
+ "redirectUris": [],
+ "webOrigins": [],
+ "notBefore": 0,
+ "bearerOnly": false,
+ "consentRequired": false,
+ "standardFlowEnabled": true,
+ "implicitFlowEnabled": false,
+ "directAccessGrantsEnabled": false,
+ "serviceAccountsEnabled": false,
+ "publicClient": false,
+ "frontchannelLogout": false,
+ "protocol": "openid-connect",
+ "attributes": {
+ "post.logout.redirect.uris": "+"
+ },
+ "authenticationFlowBindingOverrides": {},
+ "fullScopeAllowed": false,
+ "nodeReRegistrationTimeout": 0,
+ "defaultClientScopes": [
+ "web-origins",
+ "roles",
+ "profile",
+ "email"
+ ],
+ "optionalClientScopes": [
+ "address",
+ "phone",
+ "offline_access",
+ "microprofile-jwt"
+ ]
+ },
+ {
+ "id": "10e6ea34-9f1e-49ef-8e28-7eb851459694",
+ "clientId": "internal",
+ "surrogateAuthRequired": false,
+ "enabled": true,
+ "alwaysDisplayInConsole": false,
+ "clientAuthenticatorType": "client-secret",
+ "secret": "internal",
+ "redirectUris": [],
+ "webOrigins": [],
+ "notBefore": 0,
+ "bearerOnly": false,
+ "consentRequired": false,
+ "standardFlowEnabled": false,
+ "implicitFlowEnabled": false,
+ "directAccessGrantsEnabled": false,
+ "serviceAccountsEnabled": true,
+ "publicClient": false,
+ "frontchannelLogout": false,
+ "protocol": "openid-connect",
+ "attributes": {
+ "saml.assertion.signature": "false",
+ "saml.force.post.binding": "false",
+ "saml.multivalued.roles": "false",
+ "saml.encrypt": "false",
+ "post.logout.redirect.uris": "+",
+ "saml.server.signature": "false",
+ "saml.server.signature.keyinfo.ext": "false",
+ "exclude.session.state.from.auth.response": "false",
+ "saml_force_name_id_format": "false",
+ "saml.client.signature": "false",
+ "tls.client.certificate.bound.access.tokens": "false",
+ "saml.authnstatement": "false",
+ "display.on.consent.screen": "false",
+ "saml.onetimeuse.condition": "false"
+ },
+ "authenticationFlowBindingOverrides": {},
+ "fullScopeAllowed": true,
+ "nodeReRegistrationTimeout": -1,
+ "protocolMappers": [
+ {
+ "id": "ff2f50b4-5409-4789-bdda-fe731f14fbff",
+ "name": "Client IP Address",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usersessionmodel-note-mapper",
+ "consentRequired": false,
+ "config": {
+ "user.session.note": "clientAddress",
+ "userinfo.token.claim": "true",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "clientAddress",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "id": "72f9ae74-9e95-4b7b-a709-5086137410bb",
+ "name": "Client ID",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usersessionmodel-note-mapper",
+ "consentRequired": false,
+ "config": {
+ "user.session.note": "clientId",
+ "userinfo.token.claim": "true",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "clientId",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "id": "029bf6c8-5a19-4798-984c-bdb205d752d5",
+ "name": "Client Host",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usersessionmodel-note-mapper",
+ "consentRequired": false,
+ "config": {
+ "user.session.note": "clientHost",
+ "userinfo.token.claim": "true",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "clientHost",
+ "jsonType.label": "String"
+ }
+ }
+ ],
+ "defaultClientScopes": [
+ "web-origins",
+ "roles",
+ "profile",
+ "email"
+ ],
+ "optionalClientScopes": [
+ "address",
+ "phone",
+ "offline_access",
+ "microprofile-jwt"
+ ]
+ },
+ {
+ "id": "1acf7ad0-68cb-46a6-a3e4-8b2f2abecb85",
+ "clientId": "seedapp-control-center",
+ "rootUrl": "http://localhost:7419",
+ "adminUrl": "http://localhost:7419",
+ "surrogateAuthRequired": false,
+ "enabled": true,
+ "alwaysDisplayInConsole": false,
+ "clientAuthenticatorType": "client-secret",
+ "secret": "seedapp-control-center",
+ "redirectUris": [
+ "dev.localhost.ionic:*",
+ "http://127.0.0.1:*",
+ "http://localhost:*",
+ "https://127.0.0.1:*",
+ "https://localhost:*"
+ ],
+ "webOrigins": [
+ "*"
+ ],
+ "notBefore": 0,
+ "bearerOnly": false,
+ "consentRequired": false,
+ "standardFlowEnabled": true,
+ "implicitFlowEnabled": true,
+ "directAccessGrantsEnabled": false,
+ "serviceAccountsEnabled": false,
+ "publicClient": true,
+ "frontchannelLogout": false,
+ "protocol": "openid-connect",
+ "attributes": {
+ "saml.assertion.signature": "false",
+ "saml.force.post.binding": "false",
+ "saml.multivalued.roles": "false",
+ "saml.encrypt": "false",
+ "post.logout.redirect.uris": "+",
+ "saml.server.signature": "false",
+ "saml.server.signature.keyinfo.ext": "false",
+ "exclude.session.state.from.auth.response": "false",
+ "saml_force_name_id_format": "false",
+ "saml.client.signature": "false",
+ "tls.client.certificate.bound.access.tokens": "false",
+ "saml.authnstatement": "false",
+ "display.on.consent.screen": "false",
+ "saml.onetimeuse.condition": "false"
+ },
+ "authenticationFlowBindingOverrides": {},
+ "fullScopeAllowed": true,
+ "nodeReRegistrationTimeout": -1,
+ "defaultClientScopes": [
+ "web-origins",
+ "seedapp",
+ "roles",
+ "profile",
+ "email"
+ ],
+ "optionalClientScopes": [
+ "address",
+ "phone",
+ "offline_access",
+ "microprofile-jwt"
+ ]
+ },
+ {
+ "id": "898488c8-e260-41c5-a463-7ceea14d587a",
+ "clientId": "realm-management",
+ "name": "${client_realm-management}",
+ "surrogateAuthRequired": false,
+ "enabled": true,
+ "alwaysDisplayInConsole": false,
+ "clientAuthenticatorType": "client-secret",
+ "secret": "**********",
+ "redirectUris": [],
+ "webOrigins": [],
+ "notBefore": 0,
+ "bearerOnly": true,
+ "consentRequired": false,
+ "standardFlowEnabled": true,
+ "implicitFlowEnabled": false,
+ "directAccessGrantsEnabled": false,
+ "serviceAccountsEnabled": false,
+ "publicClient": false,
+ "frontchannelLogout": false,
+ "protocol": "openid-connect",
+ "attributes": {
+ "post.logout.redirect.uris": "+"
+ },
+ "authenticationFlowBindingOverrides": {},
+ "fullScopeAllowed": false,
+ "nodeReRegistrationTimeout": 0,
+ "defaultClientScopes": [
+ "web-origins",
+ "roles",
+ "profile",
+ "email"
+ ],
+ "optionalClientScopes": [
+ "address",
+ "phone",
+ "offline_access",
+ "microprofile-jwt"
+ ]
+ },
+ {
+ "id": "989d2b96-b820-4f9b-aa17-55e6488b08c8",
+ "clientId": "security-admin-console",
+ "name": "${client_security-admin-console}",
+ "rootUrl": "${authAdminUrl}",
+ "baseUrl": "/admin/seedapp/console/",
+ "surrogateAuthRequired": false,
+ "enabled": true,
+ "alwaysDisplayInConsole": false,
+ "clientAuthenticatorType": "client-secret",
+ "secret": "**********",
+ "redirectUris": [
+ "/admin/seedapp/console/*"
+ ],
+ "webOrigins": [
+ "+"
+ ],
+ "notBefore": 0,
+ "bearerOnly": false,
+ "consentRequired": false,
+ "standardFlowEnabled": true,
+ "implicitFlowEnabled": false,
+ "directAccessGrantsEnabled": false,
+ "serviceAccountsEnabled": false,
+ "publicClient": true,
+ "frontchannelLogout": false,
+ "protocol": "openid-connect",
+ "attributes": {
+ "post.logout.redirect.uris": "+",
+ "pkce.code.challenge.method": "S256"
+ },
+ "authenticationFlowBindingOverrides": {},
+ "fullScopeAllowed": false,
+ "nodeReRegistrationTimeout": 0,
+ "protocolMappers": [
+ {
+ "id": "5fd34289-c644-411a-874c-849475d9d102",
+ "name": "locale",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "locale",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "locale",
+ "jsonType.label": "String"
+ }
+ }
+ ],
+ "defaultClientScopes": [
+ "web-origins",
+ "roles",
+ "profile",
+ "email"
+ ],
+ "optionalClientScopes": [
+ "address",
+ "phone",
+ "offline_access",
+ "microprofile-jwt"
+ ]
+ },
+ {
+ "id": "6e8deddb-b4d6-4e2e-b389-b397d3f74fcd",
+ "clientId": "seed-app-gateway-client",
+ "rootUrl": "http://localhost:8081",
+ "adminUrl": "http://localhost:8081",
+ "surrogateAuthRequired": false,
+ "enabled": true,
+ "alwaysDisplayInConsole": false,
+ "clientAuthenticatorType": "client-secret",
+ "secret": "secret123",
+ "redirectUris": [
+ "dev.localhost.ionic:*",
+ "http://127.0.0.1:*",
+ "http://localhost:*",
+ "https://127.0.0.1:*",
+ "https://localhost:*",
+ "https://oauth.pstmn.io/v1/callback"
+ ],
+ "webOrigins": [
+ "*"
+ ],
+ "notBefore": 0,
+ "bearerOnly": false,
+ "consentRequired": false,
+ "standardFlowEnabled": true,
+ "implicitFlowEnabled": true,
+ "directAccessGrantsEnabled": false,
+ "serviceAccountsEnabled": false,
+ "publicClient": true,
+ "frontchannelLogout": false,
+ "protocol": "openid-connect",
+ "attributes": {
+ "saml.assertion.signature": "false",
+ "saml.force.post.binding": "false",
+ "saml.multivalued.roles": "false",
+ "saml.encrypt": "false",
+ "post.logout.redirect.uris": "+",
+ "saml.server.signature": "false",
+ "saml.server.signature.keyinfo.ext": "false",
+ "exclude.session.state.from.auth.response": "false",
+ "saml_force_name_id_format": "false",
+ "saml.client.signature": "false",
+ "tls.client.certificate.bound.access.tokens": "false",
+ "saml.authnstatement": "false",
+ "display.on.consent.screen": "false",
+ "saml.onetimeuse.condition": "false"
+ },
+ "authenticationFlowBindingOverrides": {},
+ "fullScopeAllowed": true,
+ "nodeReRegistrationTimeout": -1,
+ "defaultClientScopes": [
+ "web-origins",
+ "seedapp",
+ "roles",
+ "profile",
+ "email"
+ ],
+ "optionalClientScopes": [
+ "address",
+ "phone",
+ "offline_access",
+ "microprofile-jwt"
+ ]
+ }
+ ],
+ "clientScopes": [
+ {
+ "id": "52d73c82-423c-44a8-b2ec-1e13f4cd6065",
+ "name": "address",
+ "description": "OpenID Connect built-in scope: address",
+ "protocol": "openid-connect",
+ "attributes": {
+ "include.in.token.scope": "true",
+ "display.on.consent.screen": "true",
+ "consent.screen.text": "${addressScopeConsentText}"
+ },
+ "protocolMappers": [
+ {
+ "id": "98230752-36b9-4755-8661-a7de1926d0d4",
+ "name": "address",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-address-mapper",
+ "consentRequired": false,
+ "config": {
+ "user.attribute.formatted": "formatted",
+ "user.attribute.country": "country",
+ "user.attribute.postal_code": "postal_code",
+ "userinfo.token.claim": "true",
+ "user.attribute.street": "street",
+ "id.token.claim": "true",
+ "user.attribute.region": "region",
+ "access.token.claim": "true",
+ "user.attribute.locality": "locality"
+ }
+ }
+ ]
+ },
+ {
+ "id": "44d24405-87bf-4b37-a627-e3fdabb93f50",
+ "name": "email",
+ "description": "OpenID Connect built-in scope: email",
+ "protocol": "openid-connect",
+ "attributes": {
+ "include.in.token.scope": "true",
+ "display.on.consent.screen": "true",
+ "consent.screen.text": "${emailScopeConsentText}"
+ },
+ "protocolMappers": [
+ {
+ "id": "36800088-6d17-4c18-93e8-2bc93901d8b7",
+ "name": "email",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-property-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "email",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "email",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "id": "3ea34afd-30b5-4e5d-a836-dbda439dce6f",
+ "name": "email verified",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-property-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "emailVerified",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "email_verified",
+ "jsonType.label": "boolean"
+ }
+ }
+ ]
+ },
+ {
+ "id": "9816de82-24b7-42fe-a85a-1264868ec293",
+ "name": "seedapp",
+ "description": "seedapp specific claims",
+ "protocol": "openid-connect",
+ "attributes": {
+ "include.in.token.scope": "false",
+ "display.on.consent.screen": "false"
+ },
+ "protocolMappers": [
+ {
+ "id": "0f9c9347-aad6-4bff-94f4-e11937f2ad33",
+ "name": "langKey",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "langKey",
+ "id.token.claim": "false",
+ "access.token.claim": "false",
+ "claim.name": "langKey",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "id": "69729907-8d1c-4961-81c0-91766f548cc9",
+ "name": "roles",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-realm-role-mapper",
+ "consentRequired": false,
+ "config": {
+ "multivalued": "true",
+ "userinfo.token.claim": "true",
+ "id.token.claim": "false",
+ "access.token.claim": "true",
+ "claim.name": "roles",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "id": "336acfe2-a717-492a-9055-5b70e808f42f",
+ "name": "login",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "preferred_username",
+ "id.token.claim": "false",
+ "access.token.claim": "false",
+ "claim.name": "login",
+ "jsonType.label": "String"
+ }
+ }
+ ]
+ },
+ {
+ "id": "87d299f2-434f-4abd-8cb0-a16231acd713",
+ "name": "microprofile-jwt",
+ "description": "Microprofile - JWT built-in scope",
+ "protocol": "openid-connect",
+ "attributes": {
+ "include.in.token.scope": "true",
+ "display.on.consent.screen": "false"
+ },
+ "protocolMappers": [
+ {
+ "id": "fce09d51-cb85-4ccd-b83d-865a4d4bf650",
+ "name": "groups",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-realm-role-mapper",
+ "consentRequired": false,
+ "config": {
+ "multivalued": "true",
+ "userinfo.token.claim": "true",
+ "user.attribute": "foo",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "groups",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "id": "3d1ee7e2-b7e1-4504-bd52-b47a2cb10eec",
+ "name": "upn",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-property-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "username",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "upn",
+ "jsonType.label": "String"
+ }
+ }
+ ]
+ },
+ {
+ "id": "0399b625-22d7-4d68-b4db-fd1dc2effacc",
+ "name": "offline_access",
+ "description": "OpenID Connect built-in scope: offline_access",
+ "protocol": "openid-connect",
+ "attributes": {
+ "consent.screen.text": "${offlineAccessScopeConsentText}",
+ "display.on.consent.screen": "true"
+ }
+ },
+ {
+ "id": "2b867b2d-3373-43ff-b50f-ea37a5e1c390",
+ "name": "phone",
+ "description": "OpenID Connect built-in scope: phone",
+ "protocol": "openid-connect",
+ "attributes": {
+ "include.in.token.scope": "true",
+ "display.on.consent.screen": "true",
+ "consent.screen.text": "${phoneScopeConsentText}"
+ },
+ "protocolMappers": [
+ {
+ "id": "daa0191b-20d1-4f71-b191-6c48a37e3677",
+ "name": "phone number",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "phoneNumber",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "phone_number",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "id": "32213de7-12f7-4864-b696-c8e6c5e0c26e",
+ "name": "phone number verified",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "phoneNumberVerified",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "phone_number_verified",
+ "jsonType.label": "boolean"
+ }
+ }
+ ]
+ },
+ {
+ "id": "60a44832-9776-449f-94cd-fa8c24a75f35",
+ "name": "profile",
+ "description": "OpenID Connect built-in scope: profile",
+ "protocol": "openid-connect",
+ "attributes": {
+ "include.in.token.scope": "true",
+ "display.on.consent.screen": "true",
+ "consent.screen.text": "${profileScopeConsentText}"
+ },
+ "protocolMappers": [
+ {
+ "id": "a59584ab-7a7c-4b23-95b5-be8dbbfadc6f",
+ "name": "family name",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-property-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "lastName",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "family_name",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "id": "d382c1dc-d5d8-479e-8809-f0a618113a07",
+ "name": "website",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "website",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "website",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "id": "559f86c1-1187-498d-8354-723f4ea5721c",
+ "name": "full name",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-full-name-mapper",
+ "consentRequired": false,
+ "config": {
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "userinfo.token.claim": "true"
+ }
+ },
+ {
+ "id": "0925e106-a8e2-4ad1-b75e-4147d185894a",
+ "name": "updated at",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "updatedAt",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "updated_at",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "id": "eb8e2c73-5c65-4b53-8d55-46edef61315b",
+ "name": "locale",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "locale",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "locale",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "id": "4c109376-01bc-4b69-a3c0-4b830ecad674",
+ "name": "middle name",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "middleName",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "middle_name",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "id": "b3813956-e556-4b57-a06b-f71b0d6f3d47",
+ "name": "nickname",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "nickname",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "nickname",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "id": "28beb4c0-029b-4aa5-ad5f-6d824ca67e15",
+ "name": "username",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-property-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "username",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "preferred_username",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "id": "53d681bc-ec29-4f57-924b-ff5bd22d4093",
+ "name": "profile",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "profile",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "profile",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "id": "12ba8e12-157d-4729-918b-0d74fa444fba",
+ "name": "picture",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "picture",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "picture",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "id": "ddb818fe-8e4a-4b26-9c5d-2467a26af6dc",
+ "name": "gender",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "gender",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "gender",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "id": "f78b1746-2be1-45f4-9c1e-1f6141ccdb65",
+ "name": "birthdate",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "birthdate",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "birthdate",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "id": "7723245c-4952-4822-86ae-084048b1f2f2",
+ "name": "given name",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-property-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "firstName",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "given_name",
+ "jsonType.label": "String"
+ }
+ },
+ {
+ "id": "b192fe9f-aa82-4d7d-b8c7-eb7d1ba888d4",
+ "name": "zoneinfo",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-attribute-mapper",
+ "consentRequired": false,
+ "config": {
+ "userinfo.token.claim": "true",
+ "user.attribute": "zoneinfo",
+ "id.token.claim": "true",
+ "access.token.claim": "true",
+ "claim.name": "zoneinfo",
+ "jsonType.label": "String"
+ }
+ }
+ ]
+ },
+ {
+ "id": "d181691e-b4a6-4063-9eba-6b984402a9a7",
+ "name": "role_list",
+ "description": "SAML role list",
+ "protocol": "saml",
+ "attributes": {
+ "consent.screen.text": "${samlRoleListScopeConsentText}",
+ "display.on.consent.screen": "true"
+ },
+ "protocolMappers": [
+ {
+ "id": "724b16d4-8a9b-42d8-850f-99ca1ab3c958",
+ "name": "role list",
+ "protocol": "saml",
+ "protocolMapper": "saml-role-list-mapper",
+ "consentRequired": false,
+ "config": {
+ "single": "false",
+ "attribute.nameformat": "Basic",
+ "attribute.name": "Role"
+ }
+ }
+ ]
+ },
+ {
+ "id": "915fcb95-81da-4e4c-86ee-73f3b52c83e9",
+ "name": "roles",
+ "description": "OpenID Connect scope for add user roles to the access token",
+ "protocol": "openid-connect",
+ "attributes": {
+ "include.in.token.scope": "false",
+ "display.on.consent.screen": "true",
+ "consent.screen.text": "${rolesScopeConsentText}"
+ },
+ "protocolMappers": [
+ {
+ "id": "12f0b32d-8911-4028-809b-fc1c0e5e9207",
+ "name": "audience resolve",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-audience-resolve-mapper",
+ "consentRequired": false,
+ "config": {}
+ },
+ {
+ "id": "5b997b66-937f-46d3-9e8b-70dca949f682",
+ "name": "realm roles",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-realm-role-mapper",
+ "consentRequired": false,
+ "config": {
+ "user.attribute": "foo",
+ "access.token.claim": "true",
+ "claim.name": "realm_access.roles",
+ "jsonType.label": "String",
+ "multivalued": "true"
+ }
+ },
+ {
+ "id": "cdcd6969-a9aa-4de5-adbe-dc83da4184c5",
+ "name": "client roles",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-usermodel-client-role-mapper",
+ "consentRequired": false,
+ "config": {
+ "user.attribute": "foo",
+ "access.token.claim": "true",
+ "claim.name": "resource_access.${client_id}.roles",
+ "jsonType.label": "String",
+ "multivalued": "true"
+ }
+ }
+ ]
+ },
+ {
+ "id": "2daaac74-636f-4074-87a9-d1aba9dffb96",
+ "name": "web-origins",
+ "description": "OpenID Connect scope for add allowed web origins to the access token",
+ "protocol": "openid-connect",
+ "attributes": {
+ "include.in.token.scope": "false",
+ "display.on.consent.screen": "false",
+ "consent.screen.text": ""
+ },
+ "protocolMappers": [
+ {
+ "id": "752e035f-038d-46ac-b65d-91f863fdd986",
+ "name": "allowed web origins",
+ "protocol": "openid-connect",
+ "protocolMapper": "oidc-allowed-origins-mapper",
+ "consentRequired": false,
+ "config": {}
+ }
+ ]
+ }
+ ],
+ "defaultDefaultClientScopes": [
+ "web-origins",
+ "email",
+ "profile",
+ "roles",
+ "role_list"
+ ],
+ "defaultOptionalClientScopes": [
+ "offline_access",
+ "phone",
+ "address",
+ "microprofile-jwt"
+ ],
+ "browserSecurityHeaders": {
+ "contentSecurityPolicyReportOnly": "",
+ "xContentTypeOptions": "nosniff",
+ "xRobotsTag": "none",
+ "xFrameOptions": "SAMEORIGIN",
+ "contentSecurityPolicy": "frame-src 'self'; frame-ancestors 'self'; object-src 'none';",
+ "xXSSProtection": "1; mode=block",
+ "strictTransportSecurity": "max-age=31536000; includeSubDomains"
+ },
+ "smtpServer": {},
+ "eventsEnabled": false,
+ "eventsListeners": [
+ "jboss-logging"
+ ],
+ "enabledEventTypes": [],
+ "adminEventsEnabled": false,
+ "adminEventsDetailsEnabled": false,
+ "identityProviders": [],
+ "identityProviderMappers": [],
+ "components": {
+ "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy": [
+ {
+ "id": "827fde01-dc1b-4c1f-a529-9ef833ca3432",
+ "name": "Allowed Protocol Mapper Types",
+ "providerId": "allowed-protocol-mappers",
+ "subType": "authenticated",
+ "subComponents": {},
+ "config": {
+ "allowed-protocol-mapper-types": [
+ "oidc-usermodel-property-mapper",
+ "oidc-sha256-pairwise-sub-mapper",
+ "saml-user-attribute-mapper",
+ "saml-user-property-mapper",
+ "oidc-address-mapper",
+ "oidc-full-name-mapper",
+ "saml-role-list-mapper",
+ "oidc-usermodel-attribute-mapper"
+ ]
+ }
+ },
+ {
+ "id": "0a429e7e-be7a-46b4-b42a-d1f8b265ff16",
+ "name": "Allowed Client Scopes",
+ "providerId": "allowed-client-templates",
+ "subType": "authenticated",
+ "subComponents": {},
+ "config": {
+ "allow-default-scopes": [
+ "true"
+ ]
+ }
+ },
+ {
+ "id": "5a1ff0b4-250f-48ee-8169-abff30cf7534",
+ "name": "Allowed Client Scopes",
+ "providerId": "allowed-client-templates",
+ "subType": "anonymous",
+ "subComponents": {},
+ "config": {
+ "allow-default-scopes": [
+ "true"
+ ]
+ }
+ },
+ {
+ "id": "c79f6629-84a9-467c-81d0-63e20b19f916",
+ "name": "Full Scope Disabled",
+ "providerId": "scope",
+ "subType": "anonymous",
+ "subComponents": {},
+ "config": {}
+ },
+ {
+ "id": "b6b23ef8-96e8-4e2e-8efe-8003057a8d42",
+ "name": "Max Clients Limit",
+ "providerId": "max-clients",
+ "subType": "anonymous",
+ "subComponents": {},
+ "config": {
+ "max-clients": [
+ "200"
+ ]
+ }
+ },
+ {
+ "id": "36dfaa02-0252-4448-9cdf-a17abf239f78",
+ "name": "Trusted Hosts",
+ "providerId": "trusted-hosts",
+ "subType": "anonymous",
+ "subComponents": {},
+ "config": {
+ "host-sending-registration-request-must-match": [
+ "true"
+ ],
+ "client-uris-must-match": [
+ "true"
+ ]
+ }
+ },
+ {
+ "id": "8216421d-34fb-4726-8331-137217657bdb",
+ "name": "Allowed Protocol Mapper Types",
+ "providerId": "allowed-protocol-mappers",
+ "subType": "anonymous",
+ "subComponents": {},
+ "config": {
+ "allowed-protocol-mapper-types": [
+ "oidc-address-mapper",
+ "oidc-usermodel-attribute-mapper",
+ "saml-user-property-mapper",
+ "saml-user-attribute-mapper",
+ "saml-role-list-mapper",
+ "oidc-full-name-mapper",
+ "oidc-usermodel-property-mapper",
+ "oidc-sha256-pairwise-sub-mapper"
+ ]
+ }
+ },
+ {
+ "id": "d045f3f9-15e6-4e69-a419-0e7ff8a635ef",
+ "name": "Consent Required",
+ "providerId": "consent-required",
+ "subType": "anonymous",
+ "subComponents": {},
+ "config": {}
+ }
+ ],
+ "org.keycloak.userprofile.UserProfileProvider": [
+ {
+ "id": "b05ccf0d-d8ac-4695-bd60-37018f8f94b4",
+ "providerId": "declarative-user-profile",
+ "subComponents": {},
+ "config": {}
+ }
+ ],
+ "org.keycloak.keys.KeyProvider": [
+ {
+ "id": "62707fae-58f9-4fc2-89fb-0c5d212dc3dc",
+ "name": "rsa-generated",
+ "providerId": "rsa-generated",
+ "subComponents": {},
+ "config": {
+ "priority": [
+ "100"
+ ]
+ }
+ },
+ {
+ "id": "4a8480bc-96fd-4906-a907-f948a73bab38",
+ "name": "hmac-generated",
+ "providerId": "hmac-generated",
+ "subComponents": {},
+ "config": {
+ "priority": [
+ "100"
+ ],
+ "algorithm": [
+ "HS256"
+ ]
+ }
+ },
+ {
+ "id": "40c01a32-0c0b-4dbb-9595-e5a5c8d26bc4",
+ "name": "aes-generated",
+ "providerId": "aes-generated",
+ "subComponents": {},
+ "config": {
+ "priority": [
+ "100"
+ ]
+ }
+ }
+ ]
+ },
+ "internationalizationEnabled": false,
+ "supportedLocales": [],
+ "authenticationFlows": [
+ {
+ "id": "491fbbc9-b70b-45bd-8243-2039ae3f115d",
+ "alias": "Account verification options",
+ "description": "Method with which to verity the existing account",
+ "providerId": "basic-flow",
+ "topLevel": false,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "idp-email-verification",
+ "authenticatorFlow": false,
+ "requirement": "ALTERNATIVE",
+ "priority": 10,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticatorFlow": true,
+ "requirement": "ALTERNATIVE",
+ "priority": 20,
+ "flowAlias": "Verify Existing Account by Re-authentication",
+ "userSetupAllowed": false,
+ "autheticatorFlow": true
+ }
+ ]
+ },
+ {
+ "id": "2c63ad60-76ab-4350-9def-74328bab70d0",
+ "alias": "Authentication Options",
+ "description": "Authentication options.",
+ "providerId": "basic-flow",
+ "topLevel": false,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "basic-auth",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 10,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticator": "basic-auth-otp",
+ "authenticatorFlow": false,
+ "requirement": "DISABLED",
+ "priority": 20,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticator": "auth-spnego",
+ "authenticatorFlow": false,
+ "requirement": "DISABLED",
+ "priority": 30,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ }
+ ]
+ },
+ {
+ "id": "82b9b584-2243-4893-b58c-4567f34434a6",
+ "alias": "Browser - Conditional OTP",
+ "description": "Flow to determine if the OTP is required for the authentication",
+ "providerId": "basic-flow",
+ "topLevel": false,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "conditional-user-configured",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 10,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticator": "auth-otp-form",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 20,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ }
+ ]
+ },
+ {
+ "id": "e70e7c74-8ab5-411c-b06c-d478a452bee3",
+ "alias": "Direct Grant - Conditional OTP",
+ "description": "Flow to determine if the OTP is required for the authentication",
+ "providerId": "basic-flow",
+ "topLevel": false,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "conditional-user-configured",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 10,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticator": "direct-grant-validate-otp",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 20,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ }
+ ]
+ },
+ {
+ "id": "4f3e6fdd-9b4d-4dc0-946a-1e1ccae7af71",
+ "alias": "First broker login - Conditional OTP",
+ "description": "Flow to determine if the OTP is required for the authentication",
+ "providerId": "basic-flow",
+ "topLevel": false,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "conditional-user-configured",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 10,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticator": "auth-otp-form",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 20,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ }
+ ]
+ },
+ {
+ "id": "aa66c794-f21b-4663-9de1-9e27a7e425ab",
+ "alias": "Handle Existing Account",
+ "description": "Handle what to do if there is existing account with same email/username like authenticated identity provider",
+ "providerId": "basic-flow",
+ "topLevel": false,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "idp-confirm-link",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 10,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticatorFlow": true,
+ "requirement": "REQUIRED",
+ "priority": 20,
+ "flowAlias": "Account verification options",
+ "userSetupAllowed": false,
+ "autheticatorFlow": true
+ }
+ ]
+ },
+ {
+ "id": "2d4499a0-399c-4b6c-970c-7b441498f7b9",
+ "alias": "Reset - Conditional OTP",
+ "description": "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.",
+ "providerId": "basic-flow",
+ "topLevel": false,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "conditional-user-configured",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 10,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticator": "reset-otp",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 20,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ }
+ ]
+ },
+ {
+ "id": "710f4172-56a5-466e-bc75-ad7405ff62b5",
+ "alias": "User creation or linking",
+ "description": "Flow for the existing/non-existing user alternatives",
+ "providerId": "basic-flow",
+ "topLevel": false,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticatorConfig": "create unique user config",
+ "authenticator": "idp-create-user-if-unique",
+ "authenticatorFlow": false,
+ "requirement": "ALTERNATIVE",
+ "priority": 10,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticatorFlow": true,
+ "requirement": "ALTERNATIVE",
+ "priority": 20,
+ "flowAlias": "Handle Existing Account",
+ "userSetupAllowed": false,
+ "autheticatorFlow": true
+ }
+ ]
+ },
+ {
+ "id": "da7d3a39-7077-4354-9ffc-5b9f79fbaf0d",
+ "alias": "Verify Existing Account by Re-authentication",
+ "description": "Reauthentication of existing account",
+ "providerId": "basic-flow",
+ "topLevel": false,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "idp-username-password-form",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 10,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticatorFlow": true,
+ "requirement": "CONDITIONAL",
+ "priority": 20,
+ "flowAlias": "First broker login - Conditional OTP",
+ "userSetupAllowed": false,
+ "autheticatorFlow": true
+ }
+ ]
+ },
+ {
+ "id": "6285968e-6200-463a-a329-8c60bc8fe9fc",
+ "alias": "browser",
+ "description": "browser based authentication",
+ "providerId": "basic-flow",
+ "topLevel": true,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "auth-cookie",
+ "authenticatorFlow": false,
+ "requirement": "ALTERNATIVE",
+ "priority": 10,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticator": "auth-spnego",
+ "authenticatorFlow": false,
+ "requirement": "DISABLED",
+ "priority": 20,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticator": "identity-provider-redirector",
+ "authenticatorFlow": false,
+ "requirement": "ALTERNATIVE",
+ "priority": 25,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticatorFlow": true,
+ "requirement": "ALTERNATIVE",
+ "priority": 30,
+ "flowAlias": "forms",
+ "userSetupAllowed": false,
+ "autheticatorFlow": true
+ }
+ ]
+ },
+ {
+ "id": "10393f04-3922-40db-a622-2655dfcae45d",
+ "alias": "clients",
+ "description": "Base authentication for clients",
+ "providerId": "client-flow",
+ "topLevel": true,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "client-secret",
+ "authenticatorFlow": false,
+ "requirement": "ALTERNATIVE",
+ "priority": 10,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticator": "client-jwt",
+ "authenticatorFlow": false,
+ "requirement": "ALTERNATIVE",
+ "priority": 20,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticator": "client-secret-jwt",
+ "authenticatorFlow": false,
+ "requirement": "ALTERNATIVE",
+ "priority": 30,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticator": "client-x509",
+ "authenticatorFlow": false,
+ "requirement": "ALTERNATIVE",
+ "priority": 40,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ }
+ ]
+ },
+ {
+ "id": "4e5e164e-3c7e-4ca5-a10c-d7b817a7d468",
+ "alias": "direct grant",
+ "description": "OpenID Connect Resource Owner Grant",
+ "providerId": "basic-flow",
+ "topLevel": true,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "direct-grant-validate-username",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 10,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticator": "direct-grant-validate-password",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 20,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticatorFlow": true,
+ "requirement": "CONDITIONAL",
+ "priority": 30,
+ "flowAlias": "Direct Grant - Conditional OTP",
+ "userSetupAllowed": false,
+ "autheticatorFlow": true
+ }
+ ]
+ },
+ {
+ "id": "80f88b0b-70de-4e4c-ae56-0293558301c5",
+ "alias": "docker auth",
+ "description": "Used by Docker clients to authenticate against the IDP",
+ "providerId": "basic-flow",
+ "topLevel": true,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "docker-http-basic-authenticator",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 10,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ }
+ ]
+ },
+ {
+ "id": "821af41a-6e77-4e8c-85a6-0280d5268909",
+ "alias": "first broker login",
+ "description": "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account",
+ "providerId": "basic-flow",
+ "topLevel": true,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticatorConfig": "review profile config",
+ "authenticator": "idp-review-profile",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 10,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticatorFlow": true,
+ "requirement": "REQUIRED",
+ "priority": 20,
+ "flowAlias": "User creation or linking",
+ "userSetupAllowed": false,
+ "autheticatorFlow": true
+ }
+ ]
+ },
+ {
+ "id": "c4058fb0-ad93-4595-96ef-7d4bc5cbef4d",
+ "alias": "forms",
+ "description": "Username, password, otp and other auth forms.",
+ "providerId": "basic-flow",
+ "topLevel": false,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "auth-username-password-form",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 10,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticatorFlow": true,
+ "requirement": "CONDITIONAL",
+ "priority": 20,
+ "flowAlias": "Browser - Conditional OTP",
+ "userSetupAllowed": false,
+ "autheticatorFlow": true
+ }
+ ]
+ },
+ {
+ "id": "a2a1d056-2521-498f-b345-b7db56f9342c",
+ "alias": "http challenge",
+ "description": "An authentication flow based on challenge-response HTTP Authentication Schemes",
+ "providerId": "basic-flow",
+ "topLevel": true,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "no-cookie-redirect",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 10,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticatorFlow": true,
+ "requirement": "REQUIRED",
+ "priority": 20,
+ "flowAlias": "Authentication Options",
+ "userSetupAllowed": false,
+ "autheticatorFlow": true
+ }
+ ]
+ },
+ {
+ "id": "13e68e1b-4b44-4f21-a253-5b2dea24404b",
+ "alias": "registration",
+ "description": "registration flow",
+ "providerId": "basic-flow",
+ "topLevel": true,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "registration-page-form",
+ "authenticatorFlow": true,
+ "requirement": "REQUIRED",
+ "priority": 10,
+ "flowAlias": "registration form",
+ "userSetupAllowed": false,
+ "autheticatorFlow": true
+ }
+ ]
+ },
+ {
+ "id": "e7588789-22d4-459b-96d6-1b480520f487",
+ "alias": "registration form",
+ "description": "registration form",
+ "providerId": "form-flow",
+ "topLevel": false,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "registration-user-creation",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 20,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticator": "registration-profile-action",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 40,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticator": "registration-password-action",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 50,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticator": "registration-recaptcha-action",
+ "authenticatorFlow": false,
+ "requirement": "DISABLED",
+ "priority": 60,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ }
+ ]
+ },
+ {
+ "id": "8dc399ef-cf7d-46d5-9688-678c146ea8c4",
+ "alias": "reset credentials",
+ "description": "Reset credentials for a user if they forgot their password or something",
+ "providerId": "basic-flow",
+ "topLevel": true,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "reset-credentials-choose-user",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 10,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticator": "reset-credential-email",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 20,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticator": "reset-password",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 30,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ },
+ {
+ "authenticatorFlow": true,
+ "requirement": "CONDITIONAL",
+ "priority": 40,
+ "flowAlias": "Reset - Conditional OTP",
+ "userSetupAllowed": false,
+ "autheticatorFlow": true
+ }
+ ]
+ },
+ {
+ "id": "47ab5a7a-f67a-4a66-bdac-932ee230000d",
+ "alias": "saml ecp",
+ "description": "SAML ECP Profile Authentication Flow",
+ "providerId": "basic-flow",
+ "topLevel": true,
+ "builtIn": true,
+ "authenticationExecutions": [
+ {
+ "authenticator": "http-basic-authenticator",
+ "authenticatorFlow": false,
+ "requirement": "REQUIRED",
+ "priority": 10,
+ "userSetupAllowed": false,
+ "autheticatorFlow": false
+ }
+ ]
+ }
+ ],
+ "authenticatorConfig": [
+ {
+ "id": "b12be521-4e2b-42f0-a1a2-f1ba47ab4854",
+ "alias": "create unique user config",
+ "config": {
+ "require.password.update.after.registration": "false"
+ }
+ },
+ {
+ "id": "58bf2d56-1c45-4acc-9005-23b978d961d7",
+ "alias": "review profile config",
+ "config": {
+ "update.profile.on.first.login": "missing"
+ }
+ }
+ ],
+ "requiredActions": [
+ {
+ "alias": "CONFIGURE_TOTP",
+ "name": "Configure OTP",
+ "providerId": "CONFIGURE_TOTP",
+ "enabled": true,
+ "defaultAction": false,
+ "priority": 10,
+ "config": {}
+ },
+ {
+ "alias": "terms_and_conditions",
+ "name": "Terms and Conditions",
+ "providerId": "terms_and_conditions",
+ "enabled": false,
+ "defaultAction": false,
+ "priority": 20,
+ "config": {}
+ },
+ {
+ "alias": "UPDATE_PASSWORD",
+ "name": "Update Password",
+ "providerId": "UPDATE_PASSWORD",
+ "enabled": true,
+ "defaultAction": false,
+ "priority": 30,
+ "config": {}
+ },
+ {
+ "alias": "UPDATE_PROFILE",
+ "name": "Update Profile",
+ "providerId": "UPDATE_PROFILE",
+ "enabled": true,
+ "defaultAction": false,
+ "priority": 40,
+ "config": {}
+ },
+ {
+ "alias": "VERIFY_EMAIL",
+ "name": "Verify Email",
+ "providerId": "VERIFY_EMAIL",
+ "enabled": true,
+ "defaultAction": false,
+ "priority": 50,
+ "config": {}
+ },
+ {
+ "alias": "delete_account",
+ "name": "Delete Account",
+ "providerId": "delete_account",
+ "enabled": false,
+ "defaultAction": false,
+ "priority": 60,
+ "config": {}
+ },
+ {
+ "alias": "update_user_locale",
+ "name": "Update User Locale",
+ "providerId": "update_user_locale",
+ "enabled": true,
+ "defaultAction": false,
+ "priority": 1000,
+ "config": {}
+ }
+ ],
+ "browserFlow": "browser",
+ "registrationFlow": "registration",
+ "directGrantFlow": "direct grant",
+ "resetCredentialsFlow": "reset credentials",
+ "clientAuthenticationFlow": "clients",
+ "dockerAuthenticationFlow": "docker auth",
+ "attributes": {
+ "cibaBackchannelTokenDeliveryMode": "poll",
+ "cibaExpiresIn": "120",
+ "cibaAuthRequestedUserHint": "login_hint",
+ "oauth2DeviceCodeLifespan": "600",
+ "clientOfflineSessionMaxLifespan": "0",
+ "oauth2DevicePollingInterval": "5",
+ "clientSessionIdleTimeout": "0",
+ "clientSessionMaxLifespan": "0",
+ "parRequestUriLifespan": "60",
+ "clientOfflineSessionIdleTimeout": "0",
+ "cibaInterval": "5",
+ "frontendUrl": "",
+ "acr.loa.map": "[]"
+ },
+ "keycloakVersion": "19.0.1",
+ "userManagedAccessAllowed": false,
+ "clientProfiles": {
+ "profiles": []
+ },
+ "clientPolicies": {
+ "policies": []
+ }
+}
diff --git a/main-app/main-webapp/src/main/resources/templates/_fragments/header.html b/main-app/main-webapp/src/main/resources/templates/_fragments/header.html
index 38456ed..550d6c2 100644
--- a/main-app/main-webapp/src/main/resources/templates/_fragments/header.html
+++ b/main-app/main-webapp/src/main/resources/templates/_fragments/header.html
@@ -44,7 +44,7 @@
-
+
@@ -61,7 +61,7 @@
-
+
Login
@@ -71,7 +71,7 @@