-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathAuthCommandService.java
More file actions
116 lines (100 loc) · 5.21 KB
/
Copy pathAuthCommandService.java
File metadata and controls
116 lines (100 loc) · 5.21 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
package com.promsearch.auth.application;
import com.promsearch.auth.application.port.out.AccessTokenProvider;
import com.promsearch.auth.application.port.out.RefreshTokenProvider;
import com.promsearch.auth.application.port.out.RefreshTokenProvider.RefreshToken;
import com.promsearch.auth.application.port.out.RefreshTokenProvider.RefreshTokenClaims;
import com.promsearch.auth.application.port.out.RefreshTokenSessionRepository;
import com.promsearch.auth.application.port.out.TokenHasher;
import com.promsearch.auth.domain.RefreshTokenSession;
import com.promsearch.auth.domain.exception.AuthDomainException;
import com.promsearch.auth.domain.exception.AuthErrorCode;
import com.promsearch.user.application.AuthUserInfo;
import com.promsearch.user.application.GetUserCredentialUseCase;
import java.time.Instant;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class AuthCommandService implements LoginUseCase, ReissueUseCase {
private final GetUserCredentialUseCase getUserCredentialUseCase;
private final PasswordEncoder passwordEncoder;
private final AccessTokenProvider accessTokenProvider;
private final RefreshTokenProvider refreshTokenProvider;
private final RefreshTokenSessionRepository refreshTokenSessionRepository;
private final TokenHasher tokenHasher;
@Override
@Transactional
public LoginInfo login(LoginCommand command) {
AuthUserInfo user = getUserCredentialUseCase.findByEmail(command.email())
.orElseThrow(() -> new AuthDomainException(AuthErrorCode.INVALID_CREDENTIALS));
validatePassword(command.password(), user.encodedPassword());
validateActiveUser(user);
AuthenticatedUserInfo authenticatedUser = AuthenticatedUserInfo.from(user);
RefreshToken refreshToken = refreshTokenProvider.createRefreshToken(authenticatedUser);
saveRefreshTokenSession(authenticatedUser.userId(), refreshToken, UUID.randomUUID().toString());
return LoginInfo.of(
accessTokenProvider.createAccessToken(authenticatedUser),
refreshToken.value(),
accessTokenProvider.getAccessTokenExpirationSeconds(),
authenticatedUser,
user.name(),
user.nickname()
);
}
@Override
@Transactional
public ReissueInfo reissue(ReissueCommand command) {
RefreshTokenClaims claims = refreshTokenProvider.parse(command.refreshToken());
RefreshTokenSession session = refreshTokenSessionRepository
.findByTokenHashForUpdate(tokenHasher.hash(command.refreshToken()))
.orElseThrow(() -> new AuthDomainException(AuthErrorCode.INVALID_TOKEN));
Instant now = Instant.now();
validateRefreshTokenSession(session, claims, now);
AuthUserInfo user = getUserCredentialUseCase.findById(claims.userId())
.orElseThrow(() -> new AuthDomainException(AuthErrorCode.INVALID_TOKEN));
validateActiveUser(user);
AuthenticatedUserInfo authenticatedUser = AuthenticatedUserInfo.from(user);
session.revoke(now);
refreshTokenSessionRepository.save(session);
RefreshToken refreshToken = refreshTokenProvider.createRefreshToken(authenticatedUser);
saveRefreshTokenSession(authenticatedUser.userId(), refreshToken, session.getFamilyId());
return ReissueInfo.of(
accessTokenProvider.createAccessToken(authenticatedUser),
refreshToken.value(),
accessTokenProvider.getAccessTokenExpirationSeconds()
);
}
private void saveRefreshTokenSession(Long userId, RefreshToken refreshToken, String familyId) {
refreshTokenSessionRepository.save(RefreshTokenSession.create(
userId, tokenHasher.hash(refreshToken.value()), familyId, refreshToken.expiresAt()));
}
private void validateRefreshTokenSession(RefreshTokenSession session, RefreshTokenClaims claims, Instant now) {
if (!session.getUserId().equals(claims.userId()) || !session.getExpiresAt().equals(claims.expiresAt())) {
revokeTokenFamilyAndReject(session, now);
}
if (session.isRevoked()) {
revokeTokenFamilyAndReject(session, now);
}
if (session.isExpiredAt(now)) {
throw new AuthDomainException(AuthErrorCode.INVALID_TOKEN);
}
}
private void revokeTokenFamilyAndReject(RefreshTokenSession session, Instant now) {
refreshTokenSessionRepository.revokeFamily(session.getFamilyId(), now);
throw new AuthDomainException(AuthErrorCode.INVALID_TOKEN);
}
private void validatePassword(String rawPassword, String encodedPassword) {
if (!passwordEncoder.matches(rawPassword, encodedPassword)) {
throw new AuthDomainException(AuthErrorCode.INVALID_CREDENTIALS);
}
}
private void validateActiveUser(AuthUserInfo user) {
if (!user.active()) {
throw new AuthDomainException(AuthErrorCode.INVALID_CREDENTIALS);
}
}
}