Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/dev' into dev-deploy
Browse files Browse the repository at this point in the history
  • Loading branch information
KartVen committed Dec 6, 2024
2 parents 74ebae1 + 10cfd4b commit 19dd515
Show file tree
Hide file tree
Showing 34 changed files with 396 additions and 137 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/dev.kodemy.deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ jobs:
cd $WORKING_DIRECTORY/kodemy-api-gateway
./gradlew clean assemble -x test
cd $WORKING_DIRECTORY/kodemy-auth
./gradlew clean assemble -x test
./gradlew clean assemble
cd $WORKING_DIRECTORY/kodemy-backend
./gradlew clean assemble -x test
cd $WORKING_DIRECTORY/kodemy-notification
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,12 @@
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import pl.sknikod.kodemycommons.security.configuration.JwtConfiguration;

import java.io.IOException;
import java.util.Collections;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,26 @@
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.experimental.FieldDefaults;
import lombok.extern.slf4j.Slf4j;
import org.springframework.lang.NonNull;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.util.Assert;
import pl.sknikod.kodemycommons.exception.Authentication403Exception;
import pl.sknikod.kodemycommons.security.configuration.JwtConfiguration;

import javax.crypto.SecretKey;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
public class JwtProvider {
private final JwtConfiguration.JwtProperties jwtProperties;
private final Properties jwtProperties;
private final SecretKey key;
private static final String ISSUER = "pl.sknikod.kodemy";
private final JwtParser parser;

public JwtProvider(JwtConfiguration.JwtProperties jwtProperties) {
public JwtProvider(Properties jwtProperties) {
this.jwtProperties = jwtProperties;
this.key = generateKey(jwtProperties.getSecretKey());
this.parser = buildParser(this.key);
Expand All @@ -52,7 +52,7 @@ public Token generateDelegationToken(String subject, String authority) {
ClaimKey.AUTHORITIES, List.of(authority)));
}

public Token generateUserToken(@NonNull Input input) {
public Token generateUserToken(Input input) {
Assert.notNull(input, "input cannot be null");
var authorities = input.authorities != null && !input.authorities.isEmpty()
? input.authorities.stream().map(SimpleGrantedAuthority::getAuthority).toList()
Expand All @@ -71,8 +71,8 @@ public Token generateToken(TokenType tokenType, String subject, Map<String, ?> c
final UUID jti = UUID.randomUUID();
final Date issuedAt = new Date(System.currentTimeMillis());
final Date expiration = new Date(issuedAt.getTime() + 1000L * 60 * switch (tokenType) {
case USER_TOKEN -> jwtProperties.getBearer().getExpirationMin();
case DELEGATION_TOKEN -> jwtProperties.getDelegation().getExpirationMin();
case USER_TOKEN -> jwtProperties.getBearerExpirationMin();
case DELEGATION_TOKEN -> jwtProperties.getDelegationExpirationMin();
});
return new Token(jti, generate(jti, subject, issuedAt, expiration, claims), expiration);

Expand Down Expand Up @@ -199,4 +199,13 @@ public Deserialize(UUID bearerId, Long id, String username, Integer state, Set<S
}
}
}

@Getter
@Setter
@NoArgsConstructor
public abstract static class Properties {
private String secretKey = "";
private Integer bearerExpirationMin = 15;
private Integer delegationExpirationMin = 60;
}
}

This file was deleted.

6 changes: 6 additions & 0 deletions kodemy-auth/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ plugins {
id 'java'
id 'org.springframework.boot' version '3.2.7'
id 'io.spring.dependency-management' version '1.1.5'
id 'idea'
id 'groovy'
}

ext {
Expand Down Expand Up @@ -69,6 +71,10 @@ dependencies {
testImplementation 'org.springframework.cloud:spring-cloud-contract-wiremock'
testImplementation 'org.springframework.cloud:spring-cloud-stream'
testImplementation 'org.springframework.cloud:spring-cloud-stream-test-binder'
testImplementation 'org.spockframework:spock-spring:2.4-M4-groovy-4.0'
testImplementation 'net.bytebuddy:byte-buddy'
testImplementation "org.apache.groovy:groovy"
testImplementation "org.apache.groovy:groovy-json"
testImplementation 'org.testcontainers:junit-jupiter:1.20.0'
constraints {
testImplementation('org.apache.commons:commons-compress:1.26.2') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
Expand All @@ -23,7 +22,6 @@
import pl.sknikod.kodemycommons.exception.handler.ServletExceptionHandler;
import pl.sknikod.kodemycommons.security.JwtAuthorizationFilter;
import pl.sknikod.kodemycommons.security.JwtProvider;
import pl.sknikod.kodemycommons.security.configuration.JwtConfiguration;

import java.util.List;

Expand All @@ -33,7 +31,6 @@
securedEnabled = true,
jsr250Enabled = true)
@RequiredArgsConstructor
@Import({JwtConfiguration.class})
@EnableAutoConfiguration(exclude = UserDetailsServiceAutoConfiguration.class)
public class SecurityConfiguration {
@Bean
Expand Down Expand Up @@ -62,7 +59,7 @@ public ServletExceptionHandler servletExceptionHandler(ObjectMapper objectMapper
}

@Bean
public RestExceptionHandler restExceptionHandler(){
public RestExceptionHandler restExceptionHandler() {
return new RestExceptionHandler();
}

Expand All @@ -79,8 +76,8 @@ public JwtAuthorizationFilter jwtAuthorizationFilter(
}

@Bean
public JwtProvider jwtProvider(JwtConfiguration jwtConfiguration) {
return new JwtProvider(jwtConfiguration.getJwtProperties());
public JwtProvider jwtProvider(JwtProperties jwtProperties) {
return new JwtProvider(jwtProperties);
}

@Getter
Expand All @@ -103,4 +100,10 @@ public static class OAuth2EndpointsProperties {
public static class RoleProperties {
private String primary;
}

@Component
@NoArgsConstructor
@ConfigurationProperties(prefix = "app.security.jwt")
public static class JwtProperties extends JwtProvider.Properties {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public ProviderUser exchange(ClientRegistrationRepository repository, String cod
return new GithubUser(attributes);
}

private Email fixEmailNull(Map<String, Object> attributes, ClientRegistration clientRegistration, AccessToken accessToken) {
private String fixEmailNull(Map<String, Object> attributes, ClientRegistration clientRegistration, AccessToken accessToken) {
log.info("Fetching {}'s user emails", clientRegistration.getRegistrationId());
var headers = new HttpHeaders();
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
Expand All @@ -67,6 +67,7 @@ private Email fixEmailNull(Map<String, Object> attributes, ClientRegistration cl
.toTry(() -> new InternalError500Exception("Failed to fetch user emails"))
.map(HttpEntity::getBody)
.map(emails -> emails.stream().filter(e -> e.primary).findFirst().orElse(null))
.map(Email::getEmail)
.getOrElseThrow(ExceptionUtil::throwIfFailure);
}

Expand Down
7 changes: 3 additions & 4 deletions kodemy-auth/src/main/resources/application-local.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,11 @@ service:
baseUrl:
gateway: http://localhost:8080

jwt:
bearer:
expiration-min: 720

app:
security:
jwt:
secret-key: YWJjZGVmZ2hjvbwjrW5vcHFyc3R1dnd4eXoxMjM0NTY3OnDMTIzNDU2Nzg5MDEyMzQ1Njc4OTAf=
bearer-expiration-min: 720
oauth2:
baseUrl:
front: http://localhost:3000
Expand Down
5 changes: 2 additions & 3 deletions kodemy-auth/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,14 +71,13 @@ service:
baseUrl:
gateway: http://kodemy-api-gateway:8080

jwt:
secret-key: ${JWT_KEY}

app:
security:
cors:
credentials: true
mapping: "/**"
jwt:
secret-key: ${JWT_KEY}
oauth2:
baseUrl:
front: ${FRONTEND_PUBLIC_HOST}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package pl.sknikod.kodemyauth

import com.github.tomakehurst.wiremock.WireMockServer
import com.github.tomakehurst.wiremock.core.WireMockConfiguration
import org.springframework.boot.autoconfigure.ImportAutoConfiguration
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.cloud.stream.binder.test.TestChannelBinderConfiguration
import org.springframework.test.context.ContextConfiguration
import org.springframework.test.context.DynamicPropertyRegistry
import org.springframework.test.context.DynamicPropertySource
import org.testcontainers.containers.PostgreSQLContainer
import org.testcontainers.junit.jupiter.Container
import org.testcontainers.junit.jupiter.Testcontainers
import spock.lang.Specification

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
@ContextConfiguration(classes = KodemyAuthApplication.class)
@ImportAutoConfiguration(value = TestChannelBinderConfiguration.class)
@Testcontainers
abstract class SuperclassSpec extends Specification {
@Container
private static final PostgreSQLContainer<?> DATASOURCE
private static final WireMockServer WIREMOCK
private static int WIREMOCK_PORT = 9999

static {
DATASOURCE = new PostgreSQLContainer<>("postgres:14.1-alpine")
WIREMOCK = new WireMockServer(WireMockConfiguration.options().port(WIREMOCK_PORT))
}

@DynamicPropertySource
private static void containerProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", DATASOURCE::getJdbcUrl)
registry.add("spring.datasource.username", DATASOURCE::getUsername)
registry.add("spring.datasource.password", DATASOURCE::getPassword)

final String wiremockBaseUrl = "http://localhost:${WIREMOCK_PORT}"
Map.of(
"service.baseUrl.gateway", "${wiremockBaseUrl}/gateway",
"security.oauth2.client.registration.github.tokenUri", "${wiremockBaseUrl}/login/oauth/access_token",
"security.oauth2.client.registration.github.authorizationUri", "${wiremockBaseUrl}/login/oauth/authorize",
"security.oauth2.client.registration.github.userInfoEndpoint.uri", "${wiremockBaseUrl}/user",
"eureka.client.enabled", false,
).forEach {
key, value -> registry.add(key, { value })
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package pl.sknikod.kodemyauth.factory;

import pl.sknikod.kodemyauth.infrastructure.database.RefreshToken;

import java.time.LocalDateTime;
import java.util.UUID;

public class RefreshTokenFactory {
public static RefreshToken create(UUID token, UUID bearerJti) {
RefreshToken refreshToken = new RefreshToken(
token,
bearerJti,
LocalDateTime.now(),
UserFactory.create()
);
refreshToken.setId(1L);
return refreshToken;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package pl.sknikod.kodemyauth.factory;

import pl.sknikod.kodemyauth.infrastructure.database.Permission;
import pl.sknikod.kodemyauth.infrastructure.database.Role;

import java.util.Set;

public class RoleFactory {
public static Role create() {
Role role = new Role();
role.setId(1L);
role.setPermissions(Set.of(new Permission("PERMISSION")));
return role;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package pl.sknikod.kodemyauth.factory;

import pl.sknikod.kodemyauth.infrastructure.database.User;

public class UserFactory {
public static User create(Long userId) {
User user = new User(
"username",
"[email protected]",
null,
RoleFactory.create()
);
user.setId(userId);
return user;
}

public static User create() {
return create(1L);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package pl.sknikod.kodemyauth.infrastructure.module.auth

import spock.lang.Specification

class AccessTokenServiceSpec extends Specification {
def TOKEN = "eyJhbGciOiJIUzM4NCJ9.eyJpc3MiOiJwbC5za25pa29kLmtvZGVteSIsImp0aSI6ImQxMWI0MDhkLWYwNTMtNDIwMC04YjFkLTE2ODRkYTg4NzEyNCIsInN1YiI6IkthcnRWZW4iLCJpYXQiOjE3MzMyNjU0MDUsImV4cCI6MTczMzMwODYwNSwic3RhdGUiOjgsImF1dGhvcml0aWVzIjpbXSwiaWQiOjF9.qt751MNLNOlLqUkattm3bH0BJtVsmBQBk3g5WTcwBHJTrWNVwhNALv0TQRpRfQQJ"

def accessTokenService = new AccessTokenService()

def "should get access token"() {
when:
def result = accessTokenService.getAccessToken("Bearer " + TOKEN)

then:
result == TOKEN
}
}
Loading

0 comments on commit 19dd515

Please sign in to comment.