11package io .unityfoundation .auth ;
22
3- import io .micronaut .context .annotation .Value ;
4- import io .micronaut .http .HttpRequest ;
53import io .micronaut .http .HttpResponse ;
64import io .micronaut .http .HttpStatus ;
75import io .micronaut .http .annotation .*;
86import io .micronaut .http .exceptions .HttpStatusException ;
97import io .micronaut .security .annotation .Secured ;
10- import io .micronaut .security .rules .SecurityRule ;
118import io .micronaut .serde .annotation .Serdeable ;
129import io .unityfoundation .auth .entities .PasswordResetToken ;
1310import io .unityfoundation .auth .entities .PasswordResetTokenRepo ;
1613import jakarta .transaction .Transactional ;
1714import jakarta .validation .Valid ;
1815import 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 ;
2120import java .time .Instant ;
2221import java .time .temporal .ChronoUnit ;
22+ import java .util .HexFormat ;
2323import java .util .Optional ;
2424import java .util .UUID ;
2525
26- @ Secured (SecurityRule . IS_ANONYMOUS )
26+ @ Secured ("INTERNAL_SERVICE" )
2727@ Controller ("/api/password-reset" )
2828public 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
0 commit comments