hr guest
This commit is contained in:
@@ -202,7 +202,7 @@ publish-rag-view:
|
|||||||
before_script:
|
before_script:
|
||||||
- echo "$CI_REGISTRY_PASSWORD" | docker login $CI_REGISTRY -u $CI_REGISTRY_USER --password-stdin
|
- echo "$CI_REGISTRY_PASSWORD" | docker login $CI_REGISTRY -u $CI_REGISTRY_USER --password-stdin
|
||||||
script:
|
script:
|
||||||
- docker build -t $REGISTRY/rag-view:${CI_COMMIT_SHORT_SHA} -t $REGISTRY/rag-view:latest -f rag-view/docker/Dockerfile rag-view/
|
- docker build --build-arg VITE_GUEST_EMAIL=$VITE_GUEST_EMAIL --build-arg VITE_GUEST_PASSWORD=$VITE_GUEST_PASSWORD -t $REGISTRY/rag-view:${CI_COMMIT_SHORT_SHA} -t $REGISTRY/rag-view:latest -f rag-view/docker/Dockerfile rag-view/
|
||||||
- docker push $REGISTRY/rag-view:${CI_COMMIT_SHORT_SHA}
|
- docker push $REGISTRY/rag-view:${CI_COMMIT_SHORT_SHA}
|
||||||
- docker push $REGISTRY/rag-view:latest
|
- docker push $REGISTRY/rag-view:latest
|
||||||
needs: [build-rag-view]
|
needs: [build-rag-view]
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ export default function LoginPage() {
|
|||||||
const [password, setPassword] = useState("");
|
const [password, setPassword] = useState("");
|
||||||
const [error, setError] = useState("");
|
const [error, setError] = useState("");
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
|
|
||||||
// Handle OAuth2 callback — token & refreshToken in URL
|
// Handle OAuth2 callback — token & refreshToken in URL
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -77,14 +78,25 @@ export default function LoginPage() {
|
|||||||
<label className="block text-sm font-medium text-gray-300 mb-1.5">
|
<label className="block text-sm font-medium text-gray-300 mb-1.5">
|
||||||
Password
|
Password
|
||||||
</label>
|
</label>
|
||||||
<input
|
<div className="relative">
|
||||||
type="password"
|
<input
|
||||||
required
|
type={showPassword ? "text" : "password"}
|
||||||
value={password}
|
required
|
||||||
onChange={(e) => setPassword(e.target.value)}
|
value={password}
|
||||||
placeholder="••••••••"
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
className="w-full bg-gray-800 border border-gray-700 text-white rounded-lg px-4 py-2.5 text-sm placeholder-gray-500 focus:outline-none focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500 transition"
|
placeholder="••••••••"
|
||||||
/>
|
className="w-full bg-gray-800 border border-gray-700 text-white rounded-lg px-4 py-2.5 pr-10 text-sm placeholder-gray-500 focus:outline-none focus:border-indigo-500 focus:ring-1 focus:ring-indigo-500 transition"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setShowPassword((v) => !v)}
|
||||||
|
className="absolute inset-y-0 right-0 flex items-center px-3 text-gray-400 hover:text-gray-200 transition"
|
||||||
|
tabIndex={-1}
|
||||||
|
aria-label={showPassword ? "Hide password" : "Show password"}
|
||||||
|
>
|
||||||
|
{showPassword ? <EyeOffIcon /> : <EyeIcon />}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{error && (
|
{error && (
|
||||||
@@ -150,6 +162,25 @@ export default function LoginPage() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function EyeIcon() {
|
||||||
|
return (
|
||||||
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||||
|
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/>
|
||||||
|
<circle cx="12" cy="12" r="3"/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function EyeOffIcon() {
|
||||||
|
return (
|
||||||
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||||
|
<path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94"/>
|
||||||
|
<path d="M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19"/>
|
||||||
|
<line x1="1" y1="1" x2="23" y2="23"/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function GoogleIcon() {
|
function GoogleIcon() {
|
||||||
return (
|
return (
|
||||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
|||||||
@@ -54,6 +54,12 @@ public class AuthController {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping("/hr-guest-token")
|
||||||
|
public Mono<ResponseEntity<RagResponse<UserProfileDTO>>> hrGuestToken() {
|
||||||
|
return authService.hrGuestToken()
|
||||||
|
.map(ResponseEntity::ok);
|
||||||
|
}
|
||||||
|
|
||||||
private void addAuthCookie(ServerHttpResponse response, String token) {
|
private void addAuthCookie(ServerHttpResponse response, String token) {
|
||||||
ResponseCookie cookie = ResponseCookie.from("Authorization", token)
|
ResponseCookie cookie = ResponseCookie.from("Authorization", token)
|
||||||
.httpOnly(true)
|
.httpOnly(true)
|
||||||
|
|||||||
@@ -46,12 +46,12 @@ public class JwtTokenProvider {
|
|||||||
claims.put(USER_REGISTRATION_STATUS, user.getRegistrationStatus().name());
|
claims.put(USER_REGISTRATION_STATUS, user.getRegistrationStatus().name());
|
||||||
claims.put(SESSION_ID, sessionId);
|
claims.put(SESSION_ID, sessionId);
|
||||||
claims.put(LAST_UPDATE, LocalDateTime.now().toString());
|
claims.put(LAST_UPDATE, LocalDateTime.now().toString());
|
||||||
return createToken(claims, user.getEmail());
|
return createToken(claims, user.getEmail(), jwtValidityInMilliseconds);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String refreshToken(String token) {
|
public String refreshToken(String token) {
|
||||||
Claims claims = getAllClaimsFromToken(token);
|
Claims claims = getAllClaimsFromToken(token);
|
||||||
return createToken(claims, claims.getSubject());
|
return createToken(claims, claims.getSubject(), jwtValidityInMilliseconds);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean validateToken(String token) {
|
public boolean validateToken(String token) {
|
||||||
@@ -99,13 +99,26 @@ public class JwtTokenProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String createToken(Map<String, Object> claims, String subject) {
|
private String createToken(Map<String, Object> claims, String subject, long validityMs) {
|
||||||
return Jwts.builder()
|
return Jwts.builder()
|
||||||
.claims(claims)
|
.claims(claims)
|
||||||
.subject(subject)
|
.subject(subject)
|
||||||
.issuedAt(new Date())
|
.issuedAt(new Date())
|
||||||
.expiration(new Date(System.currentTimeMillis() + jwtValidityInMilliseconds))
|
.expiration(new Date(System.currentTimeMillis() + validityMs))
|
||||||
.signWith(secretKey)
|
.signWith(secretKey)
|
||||||
.compact();
|
.compact();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String generateHrGuestToken(@NonNull User user) {
|
||||||
|
Map<String, Object> claims = new HashMap<>();
|
||||||
|
claims.put(USER_ID, user.getId());
|
||||||
|
claims.put(USERNAME, user.getUsername());
|
||||||
|
claims.put(USER_EMAIL, user.getEmail());
|
||||||
|
claims.put(USER_ROLE, user.getRole().name());
|
||||||
|
claims.put(USER_REGISTRATION_STATUS, user.getRegistrationStatus().name());
|
||||||
|
claims.put(SESSION_ID, "guest");
|
||||||
|
claims.put(LAST_UPDATE, LocalDateTime.now().toString());
|
||||||
|
return createToken(claims, user.getEmail(), 3_600_000L);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -15,6 +15,7 @@ import com.posthub.gateway.security.JwtTokenProvider;
|
|||||||
import com.posthub.gateway.util.PasswordUtils;
|
import com.posthub.gateway.util.PasswordUtils;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
@@ -33,6 +34,8 @@ public class AuthService {
|
|||||||
private final RefreshTokenRepository refreshTokenRepository;
|
private final RefreshTokenRepository refreshTokenRepository;
|
||||||
private final JwtTokenProvider jwtTokenProvider;
|
private final JwtTokenProvider jwtTokenProvider;
|
||||||
private final PasswordEncoder passwordEncoder;
|
private final PasswordEncoder passwordEncoder;
|
||||||
|
@Value("${hr-guest.email}")
|
||||||
|
private String hrGuestEmail;
|
||||||
|
|
||||||
public Mono<RagResponse<UserProfileDTO>> login(LoginRequest request) {
|
public Mono<RagResponse<UserProfileDTO>> login(LoginRequest request) {
|
||||||
return userRepository.findByEmail(request.getEmail().toLowerCase())
|
return userRepository.findByEmail(request.getEmail().toLowerCase())
|
||||||
@@ -104,6 +107,17 @@ public class AuthService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Mono<RagResponse<UserProfileDTO>> hrGuestToken() {
|
||||||
|
return userRepository.findByEmail(hrGuestEmail)
|
||||||
|
.switchIfEmpty(Mono.error(new ResponseStatusException(
|
||||||
|
HttpStatus.NOT_FOUND, "HR guest user not found")))
|
||||||
|
.map(user -> {
|
||||||
|
String accessToken = jwtTokenProvider.generateHrGuestToken(user);
|
||||||
|
UserProfileDTO dto = toUserProfileDto(user, accessToken, null);
|
||||||
|
return RagResponse.createSuccessfulWithNewToken(dto);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private Mono<RagResponse<UserProfileDTO>> generateTokensAndBuildResponse(User user) {
|
private Mono<RagResponse<UserProfileDTO>> generateTokensAndBuildResponse(User user) {
|
||||||
return refreshTokenRepository.findByUserId(user.getId())
|
return refreshTokenRepository.findByUserId(user.getId())
|
||||||
.flatMap(existing -> {
|
.flatMap(existing -> {
|
||||||
|
|||||||
@@ -28,6 +28,9 @@ spring:
|
|||||||
token-uri: https://graph.facebook.com/v21.0/oauth/access_token
|
token-uri: https://graph.facebook.com/v21.0/oauth/access_token
|
||||||
user-info-uri: https://graph.facebook.com/v21.0/me?fields=id,name,email
|
user-info-uri: https://graph.facebook.com/v21.0/me?fields=id,name,email
|
||||||
|
|
||||||
|
# ---- Guest user (portfolio chat) ----
|
||||||
|
hr-guest:
|
||||||
|
email: ${HR_GUEST_EMAIL:}
|
||||||
|
|
||||||
# ---- R2DBC (reactive DB) ----
|
# ---- R2DBC (reactive DB) ----
|
||||||
r2dbc:
|
r2dbc:
|
||||||
@@ -114,6 +117,7 @@ auth:
|
|||||||
- /actuator/**
|
- /actuator/**
|
||||||
- /api/*/v3/api-docs/**
|
- /api/*/v3/api-docs/**
|
||||||
- /api/*/swagger-ui/**
|
- /api/*/swagger-ui/**
|
||||||
|
- /api/auth/hr-guest-token
|
||||||
admin-paths:
|
admin-paths:
|
||||||
- /api/*/admin/**
|
- /api/*/admin/**
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,10 @@ WORKDIR /app
|
|||||||
COPY package.json package-lock.json ./
|
COPY package.json package-lock.json ./
|
||||||
RUN npm ci
|
RUN npm ci
|
||||||
COPY . .
|
COPY . .
|
||||||
|
ARG VITE_GUEST_EMAIL
|
||||||
|
ARG VITE_GUEST_PASSWORD
|
||||||
|
ENV VITE_GUEST_EMAIL=$VITE_GUEST_EMAIL
|
||||||
|
ENV VITE_GUEST_PASSWORD=$VITE_GUEST_PASSWORD
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
FROM nginx:alpine
|
FROM nginx:alpine
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
export const BASE_URL = `${import.meta.env.VITE_API_BASE_URL || ""}/api/rag`;
|
export const BASE_URL = `${import.meta.env.VITE_API_BASE_URL || ""}/api/rag`;
|
||||||
export const AUTH_BASE_URL = `${import.meta.env.VITE_API_BASE_URL || ""}/api/auth`;
|
export const AUTH_BASE_URL = `${import.meta.env.VITE_API_BASE_URL || ""}/api/auth`;
|
||||||
export const METHOD_POST_QUERY = "POST";
|
export const METHOD_POST_QUERY = "POST";
|
||||||
//export const METHOD_POST_QUERY = "POST";
|
|
||||||
export const METHOD_GET_QUERY = "GET";
|
export const METHOD_GET_QUERY = "GET";
|
||||||
export const METHOD_DELETE_QUERY = "DELETE";
|
export const METHOD_DELETE_QUERY = "DELETE";
|
||||||
export const HEADER_CONTENT_TYPE = "Content-Type";
|
export const HEADER_CONTENT_TYPE = "Content-Type";
|
||||||
@@ -10,11 +9,10 @@ export const JWT_REFRESH_TOKEN = "refreshToken";
|
|||||||
export const APPLICATION_JSON = "application/json";
|
export const APPLICATION_JSON = "application/json";
|
||||||
export const USER_NAME_UNDEFINED = "userNameUndefined";
|
export const USER_NAME_UNDEFINED = "userNameUndefined";
|
||||||
export const USER_EMAIL_UNDEFINED = "userEmailUndefined";
|
export const USER_EMAIL_UNDEFINED = "userEmailUndefined";
|
||||||
export const GUEST_EMAIL = "guest@gmail.com";
|
export const GUEST_EMAIL = import.meta.env.VITE_GUEST_EMAIL;
|
||||||
export const GUEST_PASSWORD = "Guest123!";
|
export const GUEST_PASSWORD = import.meta.env.VITE_GUEST_PASSWORD;
|
||||||
export const ERROR_RESPONSE_NOT_OK = "Response not ok";
|
export const ERROR_RESPONSE_NOT_OK = "Response not ok";
|
||||||
export const ERROR_NO_RESPONSE = "No response from server";
|
export const ERROR_NO_RESPONSE = "No response from server";
|
||||||
export const PREFIX_AUTH = "/auth";
|
|
||||||
export const PREFIX_USERS = "/users";
|
export const PREFIX_USERS = "/users";
|
||||||
export const PREFIX_CHAT = "/chat";
|
export const PREFIX_CHAT = "/chat";
|
||||||
export const PREFIX_ENTRY = "/entry";
|
export const PREFIX_ENTRY = "/entry";
|
||||||
|
|||||||
Reference in New Issue
Block a user