Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,11 @@
@RequestScope
public class BaseApplicationService implements ApplicationService {
protected Mediator mediator;

protected ApplicationContext applicationContext;
protected ThreadPoolTaskExecutor mediatorTaskExecutor;

protected BaseApplicationService(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
mediator = applicationContext.getBean(Mediator.class);
mediatorTaskExecutor = applicationContext.getBean(ThreadPoolTaskExecutor.class);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,8 @@ public Object getIdentity() {
public Map<String, Object> getDetails() {
return details;
}

public Object get(String key) {
return details.getOrDefault(key, null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,8 @@ public Object getCredential() {
public Map<String, Object> getDetails() {
return details;
}

public Object get(String key) {
return details.getOrDefault(key, null);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package io.theurl.framework.utility;

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Date;

public class DateTimeUtility {
public static long toUnixTimestamp(long epochMilli) {
return epochMilli / 1000;
}

public static long toUnixTimestamp(long epochMilli, ZoneId zoneId) {
return epochMilli / 1000;
}

public static long toUnixTimestamp(java.time.LocalDateTime dateTime) {
return dateTime.atZone(ZoneId.systemDefault()).toEpochSecond();
}

public static long toUnixTimestamp(java.time.LocalDateTime dateTime, ZoneId zoneId) {
return dateTime.atZone(zoneId).toEpochSecond();
}

public static long toUnixTimestamp(java.time.ZonedDateTime dateTime) {
return dateTime.toEpochSecond();
}

public static long toUnixTimestamp(java.time.ZonedDateTime dateTime, ZoneId zoneId) {
return dateTime.withZoneSameInstant(zoneId).toEpochSecond();
}

public static long toUnixTimestamp(java.time.Instant instant) {
return instant.getEpochSecond();
}

public static long toUnixTimestamp(java.time.Instant instant, ZoneId zoneId) {
return instant.atZone(zoneId).toEpochSecond();
}

public static long toUnixTimestamp(java.util.Date date) {
return date.getTime() / 1000;
}

public static Date toDate(long unixTimestamp) {
return new Date(unixTimestamp * 1000);
}

public static Date toDate(LocalDateTime dateTime) {
return Date.from(dateTime.atZone(ZoneId.systemDefault()).toInstant());
}

public static Date toDate(LocalDateTime dateTime, ZoneId zoneId) {
return Date.from(dateTime.atZone(zoneId).toInstant());
}

public static Date toDate(java.time.ZonedDateTime dateTime) {
return Date.from(dateTime.toInstant());
}

public static Date toDate(java.time.Instant instant) {
return Date.from(instant);
}

public static LocalDateTime toLocalDateTime(long unixTimestamp) {
return LocalDateTime.ofEpochSecond(unixTimestamp, 0, ZoneId.systemDefault().getRules().getOffset(LocalDateTime.now()));
}

public static LocalDateTime toLocalDateTime(long unixTimestamp, ZoneId zoneId) {
return LocalDateTime.ofEpochSecond(unixTimestamp, 0, zoneId.getRules().getOffset(LocalDateTime.now()));
}

public static LocalDateTime toLocalDateTime(java.util.Date date) {
return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
}

public static LocalDateTime toLocalDateTime(java.util.Date date, ZoneId zoneId) {
return date.toInstant().atZone(zoneId).toLocalDateTime();
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package io.theurl.framework.utility;

public class RegexUtility {
public static final String PHONE_REGEX = "^\\+?[0-9]{7,15}$";
public static final String EMAIL_REGEX = "^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$";

public static boolean isEmail(String email) {
return email != null && email.matches("^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$");
return email != null && email.matches(EMAIL_REGEX);
}

public static boolean isPhone(String phone) {
return phone != null && phone.matches("^\\+?[0-9]{7,15}$");
return phone != null && phone.matches(PHONE_REGEX);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package io.theurl.identity.application.command;

import com.neroyun.mediator.Command;

public record TokenRevokeCommand(String jti, String reason) implements Command {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package io.theurl.identity.application.command;

import com.neroyun.mediator.Command;

/**
* Command to add external authority to a user.
*
* @param id the ID of the user
* @param provider the external authority provider
* @param openId the open ID of the external authority
* @param name the name of the external authority
*/
public record UserAuthorityCreateCommand(Long id, String provider, String openId, String name) implements Command {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package io.theurl.identity.application.command;

import com.neroyun.mediator.Command;

/**
* Command to remove external authority from a user.
*
* @param id the ID of the user
* @param provider the external authority provider
* @param openId the open ID of the external authority
*/
public record UserAuthorityRemoveCommand(Long id, String provider, String openId) implements Command {
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,21 @@
package io.theurl.identity.application.command;

public class UserUpdateCommand {
import com.neroyun.mediator.Command;
import lombok.Getter;

import java.util.HashMap;
import java.util.Map;

public class UserUpdateCommand implements Command {
@Getter
private final Long id;

@Getter
private final Map<String, Object> modifications = new HashMap<>();

public UserUpdateCommand(Long id) {
this.id = id;
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,32 @@ public interface UserApplicationService extends ApplicationService {
* @return A CompletableFuture representing the asynchronous operation.
*/
CompletableFuture<Void> changePasswordAsync(String oldPassword, String newPassword);

/**
* Change the email of the currently authenticated user asynchronously.
*
* @param email The new email to be set for the user.
* @return A CompletableFuture representing the asynchronous operation.
*/
CompletableFuture<Void> changeEmailAsync(String email);

/**
* Change the phone number of the currently authenticated user asynchronously.
*
* @param phone The new phone number to be set for the user.
* @return A CompletableFuture representing the asynchronous operation.
*/
CompletableFuture<Void> changePhoneAsync(String phone);

/**
* Change the nickname of the currently authenticated user asynchronously.
*
* @param nickname The new nickname to be set for the user.
* @return A CompletableFuture representing the asynchronous operation.
*/
CompletableFuture<Void> changeNicknameAsync(String nickname);

CompletableFuture<Void> connectAuthorityAsync(String provider, String code);

CompletableFuture<Void> removeAuthorityAsync(String provider, String openId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package io.theurl.identity.application.dto;

import lombok.Data;

@Data
public class UserUpdateRequestDto {
private String email;
private String phone;
private String nickname;
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,27 @@
package io.theurl.identity.application.event;

import com.neroyun.mediator.Event;
import io.theurl.framework.domain.ApplicationEvent;
import lombok.Getter;
import lombok.Setter;

import java.time.LocalDateTime;

@Getter
@Setter
public class TokenGrantedEvent extends ApplicationEvent implements Event {

private final String jti;
private final Long userId;
private final String content;
private LocalDateTime expiresAt;
private LocalDateTime issuedAt;

public TokenGrantedEvent(String jti, Long userId, String content) {
this.jti = jti;
this.userId = userId;
this.content = content;
}


public class TokenGrantedEvent extends ApplicationEvent {
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
package io.theurl.identity.application.event;

import com.neroyun.mediator.Event;
import io.theurl.framework.domain.ApplicationEvent;
import lombok.Getter;

/**
* TokenRefreshedEvent is an application event that is published when a JWT token is refreshed.
* It contains the JTI (JWT ID) of the refreshed token, which can be used to track token refresh events and perform actions such as invalidating old tokens or logging refresh activity.
*/
@Getter
public final class TokenRefreshedEvent extends ApplicationEvent implements Event {
private final String jti;

public TokenRefreshedEvent(String jti) {
this.jti = jti;
}

public class TokenRefreshedEvent extends ApplicationEvent {
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import lombok.EqualsAndHashCode;

import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;

@EqualsAndHashCode(callSuper = true)
Expand All @@ -17,4 +18,16 @@ public class UserAuthFailureEvent extends ApplicationEvent implements Event {
private LocalDateTime grantTime;
private Map<String, String> data;
private String error;

public void setData(String key, Object value) {
if (data == null) {
data = new HashMap<>();
}

if (value == null) {
return;
}

this.data.put(key, String.valueOf(value));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

@EqualsAndHashCode(callSuper = true)
@Data
public final class UserAuthSuccessEvent extends ApplicationEvent implements Event {
public class UserAuthSuccessEvent extends ApplicationEvent implements Event {
private final String grantType;
private final Long userId;

Expand All @@ -25,5 +25,17 @@ public UserAuthSuccessEvent(String grantType, Long userId) {
private LocalDateTime grantTime;

@Getter
private Map<String, String> data = new HashMap<>();
private Map<String, Object> data = new HashMap<>();

public void setData(String key, Object value) {
if (data == null) {
data = new HashMap<>();
}

if (value == null) {
return;
}

this.data.put(key, value);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package io.theurl.identity.application.handler;

import com.neroyun.mediator.Handler;
import io.theurl.framework.core.BeanScope;
import io.theurl.identity.application.command.TokenRevokeCommand;
import io.theurl.identity.domain.enums.TokenStatus;
import io.theurl.identity.domain.repository.TokenRepository;
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.stereotype.Component;

import java.util.concurrent.CompletableFuture;

@Component
@Scope(value = BeanScope.REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class TokenRevokeCommandHandler implements Handler<TokenRevokeCommand, Void> {
private final TokenRepository repository;

public TokenRevokeCommandHandler(TokenRepository repository) {
this.repository = repository;
}

@Override
public CompletableFuture<Void> handleAsync(TokenRevokeCommand message) {
var token = repository.findByJti(message.jti());
if (token != null) {
token.revoke(TokenStatus.valueOf(message.reason().toLowerCase()));
repository.save(token);
}
return CompletableFuture.completedFuture(null);
}
}
Loading
Loading