Skip to content

Commit 7210fe1

Browse files
committed
Rework s2s auth
1 parent 02298bc commit 7210fe1

13 files changed

Lines changed: 95 additions & 47 deletions

UnityAuth/build.gradle

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ dependencies {
4646
application {
4747
mainClass.set("io.unityfoundation.Application")
4848
}
49+
java {
50+
sourceCompatibility = JavaVersion.toVersion("21")
51+
targetCompatibility = JavaVersion.toVersion("21")
52+
}
4953

5054
graalvmNative.toolchainDetection = false
5155

-1.88 KB
Binary file not shown.

UnityAuth/gradle/wrapper/gradle-wrapper.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
distributionBase=GRADLE_USER_HOME
22
distributionPath=wrapper/dists
3-
distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
3+
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
44
networkTimeout=10000
55
validateDistributionUrl=true
66
zipStoreBase=GRADLE_USER_HOME

UnityAuth/gradlew

Lines changed: 5 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

UnityAuth/gradlew.bat

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

UnityAuth/src/main/java/io/unityfoundation/auth/AuthController.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,14 @@
77
import io.micronaut.http.exceptions.HttpStatusException;
88
import io.micronaut.security.annotation.Secured;
99
import io.micronaut.security.authentication.Authentication;
10-
import io.micronaut.security.rules.SecurityRule;
1110
import io.micronaut.serde.annotation.Serdeable;
1211
import io.unityfoundation.auth.entities.*;
1312
import io.unityfoundation.auth.entities.Service.ServiceStatus;
1413
import jakarta.validation.constraints.NotNull;
1514
import java.util.List;
1615
import java.util.Optional;
1716

18-
@Secured(SecurityRule.IS_AUTHENTICATED)
17+
@Secured("USER")
1918
@Controller("/api")
2019
public class AuthController {
2120

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package io.unityfoundation.auth;
2+
3+
import io.micronaut.security.token.reader.HttpHeaderTokenReader;
4+
import jakarta.inject.Singleton;
5+
6+
@Singleton
7+
public class InternalAuthTokenReader extends HttpHeaderTokenReader {
8+
9+
@Override
10+
protected String getPrefix() {
11+
return null;
12+
}
13+
14+
@Override
15+
protected String getHeaderName() {
16+
return "X-Unity-Auth-Internal";
17+
}
18+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package io.unityfoundation.auth;
2+
3+
import io.micronaut.context.annotation.Value;
4+
import io.micronaut.http.HttpRequest;
5+
import io.micronaut.security.authentication.Authentication;
6+
import io.micronaut.security.token.validator.TokenValidator;
7+
import jakarta.inject.Singleton;
8+
import org.reactivestreams.Publisher;
9+
import reactor.core.publisher.Mono;
10+
11+
import java.nio.charset.StandardCharsets;
12+
import java.security.MessageDigest;
13+
import java.util.List;
14+
15+
@Singleton
16+
public class InternalAuthTokenValidator implements TokenValidator<HttpRequest<?>> {
17+
18+
@Value("${unity.auth.internal-token}")
19+
private String internalToken;
20+
21+
@Override
22+
public Publisher<Authentication> validateToken(String token, HttpRequest<?> request) {
23+
if (internalToken != null && MessageDigest.isEqual(
24+
internalToken.getBytes(StandardCharsets.UTF_8),
25+
token.getBytes(StandardCharsets.UTF_8))) {
26+
return Mono.just(Authentication.build("internal-service", List.of("INTERNAL_SERVICE")));
27+
}
28+
return Mono.empty();
29+
}
30+
}

UnityAuth/src/main/java/io/unityfoundation/auth/PasswordResetController.java

Lines changed: 20 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,10 @@
11
package io.unityfoundation.auth;
22

3-
import io.micronaut.context.annotation.Value;
4-
import io.micronaut.http.HttpRequest;
53
import io.micronaut.http.HttpResponse;
64
import io.micronaut.http.HttpStatus;
75
import io.micronaut.http.annotation.*;
86
import io.micronaut.http.exceptions.HttpStatusException;
97
import io.micronaut.security.annotation.Secured;
10-
import io.micronaut.security.rules.SecurityRule;
118
import io.micronaut.serde.annotation.Serdeable;
129
import io.unityfoundation.auth.entities.PasswordResetToken;
1310
import io.unityfoundation.auth.entities.PasswordResetTokenRepo;
@@ -16,67 +13,55 @@
1613
import jakarta.transaction.Transactional;
1714
import jakarta.validation.Valid;
1815
import jakarta.validation.constraints.NotBlank;
19-
import jakarta.validation.constraints.NotNull;
2016

17+
import java.nio.charset.StandardCharsets;
18+
import java.security.MessageDigest;
19+
import java.security.NoSuchAlgorithmException;
2120
import java.time.Instant;
2221
import java.time.temporal.ChronoUnit;
22+
import java.util.HexFormat;
2323
import java.util.Optional;
2424
import java.util.UUID;
2525

26-
@Secured(SecurityRule.IS_ANONYMOUS)
26+
@Secured("INTERNAL_SERVICE")
2727
@Controller("/api/password-reset")
2828
public class PasswordResetController {
2929

3030
private final UserRepo userRepo;
3131
private final PasswordResetTokenRepo tokenRepo;
3232
private final PasswordEncoder passwordEncoder;
3333

34-
@Value("${unity.auth.internal-token}")
35-
protected String internalToken;
36-
3734
public PasswordResetController(UserRepo userRepo, PasswordResetTokenRepo tokenRepo, PasswordEncoder passwordEncoder) {
3835
this.userRepo = userRepo;
3936
this.tokenRepo = tokenRepo;
4037
this.passwordEncoder = passwordEncoder;
4138
}
4239

4340
@Post("/generate")
44-
public HttpResponse<GenerateTokenResponse> generateToken(@Body @Valid GenerateTokenRequest request, HttpRequest<?> httpRequest) {
45-
String authHeader = httpRequest.getHeaders().get("X-Unity-Auth-Internal");
46-
if (internalToken == null || !internalToken.equals(authHeader)) {
47-
return HttpResponse.status(HttpStatus.FORBIDDEN);
48-
}
49-
41+
public HttpResponse<GenerateTokenResponse> generateToken(@Body @Valid GenerateTokenRequest request) {
5042
Optional<User> userOptional = userRepo.findByEmail(request.email());
5143
if (userOptional.isEmpty()) {
52-
// We return 200 even if user not found for security reasons in public APIs,
53-
// but this is an internal API so we can be more explicit if we want.
54-
// Let's stay explicit for internal use.
5544
throw new HttpStatusException(HttpStatus.NOT_FOUND, "User not found");
5645
}
5746

5847
User user = userOptional.get();
5948
tokenRepo.deleteByUserId(user.getId());
6049

50+
String rawToken = UUID.randomUUID().toString();
6151
PasswordResetToken token = new PasswordResetToken();
62-
token.setToken(UUID.randomUUID().toString());
52+
token.setToken(sha256(rawToken));
6353
token.setUserId(user.getId());
6454
token.setExpiry(Instant.now().plus(1, ChronoUnit.HOURS));
6555
tokenRepo.save(token);
6656

67-
return HttpResponse.ok(new GenerateTokenResponse(token.getToken()));
57+
return HttpResponse.ok(new GenerateTokenResponse(rawToken));
6858
}
6959

7060
@Post("/reset")
7161
@Transactional
72-
public HttpResponse<?> resetPassword(@Body @Valid ResetPasswordRequest request, HttpRequest<?> httpRequest) {
73-
String authHeader = httpRequest.getHeaders().get("X-Unity-Auth-Internal");
74-
if (internalToken == null || !internalToken.equals(authHeader)) {
75-
return HttpResponse.status(HttpStatus.FORBIDDEN);
76-
}
62+
public HttpResponse<?> resetPassword(@Body @Valid ResetPasswordRequest request) {
63+
Optional<PasswordResetToken> tokenOptional = tokenRepo.findByToken(sha256(request.token()));
7764

78-
Optional<PasswordResetToken> tokenOptional = tokenRepo.findByToken(request.token());
79-
8065
if (tokenOptional.isEmpty()) {
8166
throw new HttpStatusException(HttpStatus.BAD_REQUEST, "Invalid token");
8267
}
@@ -101,6 +86,15 @@ public HttpResponse<?> resetPassword(@Body @Valid ResetPasswordRequest request,
10186
return HttpResponse.ok();
10287
}
10388

89+
private static String sha256(String input) {
90+
try {
91+
MessageDigest digest = MessageDigest.getInstance("SHA-256");
92+
return HexFormat.of().formatHex(digest.digest(input.getBytes(StandardCharsets.UTF_8)));
93+
} catch (NoSuchAlgorithmException e) {
94+
throw new RuntimeException(e);
95+
}
96+
}
97+
10498
@Serdeable
10599
public record GenerateTokenRequest(@NotBlank String email) {}
106100

UnityAuth/src/main/java/io/unityfoundation/auth/UnityAuthenticationProvider.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import reactor.core.publisher.Mono;
1717
import reactor.core.scheduler.Schedulers;
1818

19+
import java.util.List;
1920
import java.util.Map;
2021
import java.util.Objects;
2122

@@ -68,6 +69,7 @@ private User findUser(AuthenticationRequest<?, ?> authRequest) {
6869
} else {
6970
return Mono.just(AuthenticationResponse.success(
7071
(String) authenticationRequest.getIdentity(),
72+
List.of("USER"),
7173
Map.of(
7274
"first_name", Objects.toString(user.getFirstName(), ""),
7375
"last_name", Objects.toString(user.getLastName(), "")

0 commit comments

Comments
 (0)