Skip to content

Commit

Permalink
feat(access-tokens): added endpoint and separate table for external a…
Browse files Browse the repository at this point in the history
…ccess tokens
  • Loading branch information
zZHorizonZz committed Aug 18, 2024
1 parent 18a5520 commit 25a3702
Show file tree
Hide file tree
Showing 27 changed files with 300 additions and 99 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package dev.cloudeko.zenei.application.web.model.response;

import com.fasterxml.jackson.annotation.JsonProperty;
import dev.cloudeko.zenei.domain.model.account.ExternalAccessToken;
import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.eclipse.microprofile.openapi.annotations.media.Schema;

@Data
@NoArgsConstructor
@RegisterForReflection
@Schema(name = "External Access Token", description = "Represents an access token from an external provider")
public class ExternalAccessTokenResponse {

@JsonProperty("id_token")
@Schema(description = "ID token")
private String idToken;

@JsonProperty("refresh_token")
@Schema(description = "Refresh token")
private String refreshToken;

@JsonProperty("access_token")
@Schema(description = "Access token")
private String accessToken;

@JsonProperty("token_type")
@Schema(description = "Token type")
private String tokenType;

@JsonProperty("scope")
@Schema(description = "Scope")
private String scope;

@JsonProperty("access_token_expires_at")
private Long accessTokenExpiresAt;

public ExternalAccessTokenResponse(ExternalAccessToken externalAccessToken) {
this.idToken = externalAccessToken.getIdToken();
this.refreshToken = externalAccessToken.getRefreshToken();
this.accessToken = externalAccessToken.getAccessToken();
this.tokenType = externalAccessToken.getTokenType();
this.scope = externalAccessToken.getScope();
this.accessTokenExpiresAt = externalAccessToken.getAccessTokenExpiresAt();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package dev.cloudeko.zenei.application.web.model.response;

import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.NoArgsConstructor;
import org.eclipse.microprofile.openapi.annotations.media.Schema;

import java.util.ArrayList;
import java.util.List;

@NoArgsConstructor
@RegisterForReflection
@Schema(name = "External Access Token List", description = "Represents a list of external access tokens")
public class ExternalAccessTokensResponse extends ArrayList<ExternalAccessTokenResponse> {

public ExternalAccessTokensResponse(List<ExternalAccessTokenResponse> externalAccessTokens) {
super(externalAccessTokens);
}
}
Original file line number Diff line number Diff line change
@@ -1,22 +1,18 @@
package dev.cloudeko.zenei.application.web.model.response;

import com.fasterxml.jackson.annotation.JsonProperty;
import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.eclipse.microprofile.openapi.annotations.media.Schema;

import java.util.ArrayList;
import java.util.List;

@Data
@NoArgsConstructor
@AllArgsConstructor
@RegisterForReflection
@Schema(name = "Users", description = "Represents a list of users")
public class PrivateUsersResponse {
public class PrivateUsersResponse extends ArrayList<PrivateUserResponse> {

@JsonProperty("users")
@Schema(description = "A list of users")
private List<PrivateUserResponse> users;
public PrivateUsersResponse(List<PrivateUserResponse> users) {
super(users);
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package dev.cloudeko.zenei.application.web.resource;

import dev.cloudeko.zenei.application.web.model.response.ExternalAccessTokenResponse;
import dev.cloudeko.zenei.application.web.model.response.ExternalAccessTokensResponse;
import dev.cloudeko.zenei.application.web.model.response.PrivateUserResponse;
import dev.cloudeko.zenei.application.web.model.response.PrivateUsersResponse;
import dev.cloudeko.zenei.domain.feature.ListExternalAccessTokens;
import dev.cloudeko.zenei.domain.feature.ListUsers;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import lombok.AllArgsConstructor;
Expand All @@ -22,6 +22,7 @@
public class AdminUsersResource {

private final ListUsers listUsers;
private final ListExternalAccessTokens listExternalAccessTokens;

@GET
@Produces(MediaType.APPLICATION_JSON)
Expand All @@ -31,4 +32,14 @@ public Response getUsers(@QueryParam("page") Optional<Integer> page, @QueryParam

return Response.ok(new PrivateUsersResponse(usersResponse)).build();
}

@GET
@Path("{userId}/external_access_tokens/{provider}")
@Produces(MediaType.APPLICATION_JSON)
public Response getExternalAccessToken(@PathParam("userId") Long userId, @PathParam("provider") String provider) {
final var externalAccessTokens = listExternalAccessTokens.listByProvider(userId, provider);
final var externalAccessTokensResponse = externalAccessTokens.stream().map(ExternalAccessTokenResponse::new).toList();

return Response.ok(new ExternalAccessTokensResponse(externalAccessTokensResponse)).build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package dev.cloudeko.zenei.domain.feature;

import dev.cloudeko.zenei.domain.model.account.ExternalAccessToken;

import java.util.List;

public interface ListExternalAccessTokens {
List<ExternalAccessToken> listByProvider(long userId, String provider);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package dev.cloudeko.zenei.domain.feature.impl;

import dev.cloudeko.zenei.domain.feature.ListExternalAccessTokens;
import dev.cloudeko.zenei.domain.mapping.ExternalAccessTokenMapper;
import dev.cloudeko.zenei.domain.model.account.ExternalAccessToken;
import dev.cloudeko.zenei.domain.model.account.ExternalAccessTokenRepository;
import jakarta.enterprise.context.ApplicationScoped;
import lombok.AllArgsConstructor;

import java.util.List;

@ApplicationScoped
@AllArgsConstructor
public class ListExternalAccessTokensImpl implements ListExternalAccessTokens {

private final ExternalAccessTokenMapper externalAccessTokenMapper;
private final ExternalAccessTokenRepository externalAccessTokenRepository;

@Override
public List<ExternalAccessToken> listByProvider(long userId, String provider) {
return externalAccessTokenRepository.listByProvider(userId, provider);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
import dev.cloudeko.zenei.domain.feature.util.TokenUtil;
import dev.cloudeko.zenei.domain.model.Token;
import dev.cloudeko.zenei.domain.model.account.Account;
import dev.cloudeko.zenei.domain.model.account.AccountRepository;
import dev.cloudeko.zenei.domain.model.account.ExternalAccessToken;
import dev.cloudeko.zenei.domain.model.account.ExternalAccountRepository;
import dev.cloudeko.zenei.domain.model.email.EmailAddress;
import dev.cloudeko.zenei.domain.model.token.RefreshTokenRepository;
import dev.cloudeko.zenei.domain.model.user.User;
Expand All @@ -20,7 +21,7 @@
import dev.cloudeko.zenei.extension.external.ExternalAuthResolver;
import dev.cloudeko.zenei.extension.external.ExternalUserProfile;
import dev.cloudeko.zenei.extension.external.providers.AvailableProvider;
import dev.cloudeko.zenei.extension.external.web.client.ExternalAccessToken;
import dev.cloudeko.zenei.extension.external.web.client.ExternalProviderAccessToken;
import dev.cloudeko.zenei.extension.external.web.client.LoginOAuthClient;
import dev.cloudeko.zenei.infrastructure.config.ApplicationConfig;
import io.quarkus.rest.client.reactive.QuarkusRestClientBuilder;
Expand All @@ -39,7 +40,7 @@ public class LoginUserWithAuthorizationCodeImpl implements LoginUserWithAuthoriz
private final ApplicationConfig config;

private final UserRepository userRepository;
private final AccountRepository accountRepository;
private final ExternalAccountRepository externalAccountRepository;
private final RefreshTokenRepository refreshTokenRepository;

private final RefreshTokenProvider refreshTokenProvider;
Expand Down Expand Up @@ -95,16 +96,20 @@ private Optional<ExternalUserProfile.ExternalUserEmail> getEmailFromUserProfile(
private User createUserFromExternalUserProfile(ExternalUserProfile externalUserProfile,
ExternalUserProfile.ExternalUserEmail externalEmail,
String provider,
ExternalAccessToken accessToken) {
ExternalProviderAccessToken accessToken) {

final var emailAddress = EmailAddress.builder().email(externalEmail.email()).emailVerified(true).build();
final var account = Account.builder()
.provider(provider)
.providerId(externalUserProfile.getId())
.scope(accessToken.getScope())
final var externalAccessToken = ExternalAccessToken.builder()
.accessToken(accessToken.getAccessToken())
.refreshToken(accessToken.getRefreshToken())
.tokenType(accessToken.getTokenType())
.scope(accessToken.getScope())
.build();

final var account = Account.builder()
.provider(provider)
.providerId(externalUserProfile.getId())
.accessTokens(new ArrayList<>(List.of(externalAccessToken)))
.build();

final var user = User.builder()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package dev.cloudeko.zenei.domain.mapping;

import dev.cloudeko.zenei.domain.model.account.ExternalAccessToken;
import dev.cloudeko.zenei.infrastructure.repository.hibernate.entity.ExternalAccessTokenEntity;
import org.mapstruct.Mapper;
import org.mapstruct.MappingTarget;

import java.util.List;

@Mapper(config = QuarkusMappingConfig.class)
public interface ExternalAccessTokenMapper {
List<ExternalAccessToken> toDomainList(List<ExternalAccessTokenEntity> entities);

ExternalAccessToken toDomain(ExternalAccessTokenEntity entity);

void updateDomainFromEntity(ExternalAccessTokenEntity entity, @MappingTarget ExternalAccessToken domain);

ExternalAccessTokenEntity toEntity(ExternalAccessToken domain);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import lombok.NoArgsConstructor;

import java.time.LocalDateTime;
import java.util.List;

@Data
@Builder
Expand All @@ -18,14 +19,8 @@ public class Account {
private String provider;
private String providerId;

private String refreshToken;
private String accessToken;
private Long accessTokenExpiresAt;
private List<ExternalAccessToken> accessTokens;

private String tokenType;
private String scope;

private String idToken;
private String sessionState;

private LocalDateTime createdAt;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package dev.cloudeko.zenei.domain.model.account;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ExternalAccessToken {
private String idToken;
private String refreshToken;
private String accessToken;
private String tokenType;
private String scope;
private Long accessTokenExpiresAt;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package dev.cloudeko.zenei.domain.model.account;

import java.util.List;

public interface ExternalAccessTokenRepository {

List<ExternalAccessToken> listByProvider(long userId, String provider);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@

import java.util.Optional;

public interface AccountRepository {
public interface ExternalAccountRepository {
Optional<Account> findByProviderId(String providerId);
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,40 @@
package dev.cloudeko.zenei.infrastructure.repository.hibernate.entity;

public class ExternalAccessTokenEntity {
import io.quarkus.hibernate.orm.panache.PanacheEntity;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@Entity
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "external_access_tokens")
@NamedQueries({
@NamedQuery(name = "ExternalAccessTokenEntity.listByProvider", query = "SELECT e FROM ExternalAccessTokenEntity e WHERE e.account.user.id = :user AND e.account.provider = :provider")
})
public class ExternalAccessTokenEntity extends PanacheEntity {

@ManyToOne
@JoinColumn(name = "external_account_id", referencedColumnName = "id", nullable = false)
private ExternalAccountEntity account;

@Column(name = "id_token")
private String idToken;

@Column(name = "refresh_token")
private String refreshToken;

@Column(name = "access_token")
private String accessToken;

@Column(name = "token_type")
private String tokenType;

@Column(name = "scope")
private String scope;

@Column(name = "access_token_expires_at")
private Long accessTokenExpiresAt;
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import org.hibernate.annotations.UpdateTimestamp;

import java.time.LocalDateTime;
import java.util.List;

@Data
@Entity
Expand All @@ -34,23 +35,8 @@ public class ExternalAccountEntity extends PanacheEntity {
@Column(name = "provider_id")
private String providerId;

@Column(name = "refresh_token")
private String refreshToken;

@Column(name = "access_token")
private String accessToken;

@Column(name = "access_token_expires_at")
private Long accessTokenExpiresAt;

@Column(name = "token_type")
private String tokenType;

@Column(name = "scope")
private String scope;

@Column(name = "id_token")
private String idToken;
@OneToMany(mappedBy = "account", cascade = CascadeType.ALL, orphanRemoval = true)
private List<ExternalAccessTokenEntity> accessTokens;

@Column(name = "session_state")
private String sessionState;
Expand Down
Loading

0 comments on commit 25a3702

Please sign in to comment.