Repository: Timing-ProjectLAB/Timing-BE
Pull request #27: feat: [#26] 세션 인증방식으로 기능 업데이트
Author: login: sangkyounge
Created: 2025-05-17T08:44:16Z | Merged: 2025-05-18T10:42:07Z

[#26] 이슈내용처럼 로그인할경우 사용자 인증 후 응답을 기준으로 로그인될 경우 세션 생성하도록 업데이트 했습니다. 또한 application.properties 설정에 세션 시간 30분으로 타임아웃을 설정해놓았습니다.

내용이 다 관련있고 큰 내용은 AuthController 부분뿐이라 한꺼번에 함께 올렸습니다! 커밋메시지만 다르게해서 구분 쉽게되게 올려봤습니다 확인 후 피드백 or Approve 부탁드릴게요

@yunsammy 


Diff:
diff --git a/src/main/java/com/jnu/projectlab/auth/AuthController.java b/src/main/java/com/jnu/projectlab/auth/AuthController.java
new file mode 100644
index 0000000..313a5a8
--- /dev/null
+++ b/src/main/java/com/jnu/projectlab/auth/AuthController.java
@@ -0,0 +1,101 @@
+package com.jnu.projectlab.auth;
+
+import com.jnu.projectlab.user.DuplicatedUserException;
+import com.jnu.projectlab.user.UserDto;
+import com.jnu.projectlab.user.UserService;
+import jakarta.servlet.http.HttpSession;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.HashMap;
+import java.util.Map;
+import jakarta.servlet.http.HttpServletRequest;
+
+@RestController
+@RequestMapping("/auth")
+public class AuthController {
+
+    @Autowired
+    private UserService userService;
+
+    @Autowired
+    private AuthenticationManager authenticationManager;
+
+    /**
+     * 사용자 로그인을 처리하는 엔드포인트
+     * 
+     * @param loginRequest 로그인 요청 정보 (사용자 ID와 비밀번호 포함)
+     * @return ResponseEntity<?> 로그인 성공 시 성공 메시지, 실패 시 401 Unauthorized
+     */
+    @PostMapping("/login")
+    public ResponseEntity<?> login(@RequestBody LoginRequest loginRequest) {
+        try {
+            // 인증 매니저를 통해 사용자 인증 시도
+            Authentication authentication = authenticationManager.authenticate(
+                new UsernamePasswordAuthenticationToken(
+                    loginRequest.getUserId(),
+                    loginRequest.getPassword()
+                )
+            );
+
+            // 인증 성공 시 SecurityContext에 인증 정보 저장
+            SecurityContextHolder.getContext().setAuthentication(authentication);
+
+            // 성공 응답 생성
+            Map<String, Object> response = new HashMap<>();
+            response.put("message", "로그인이 성공적으로 완료되었습니다.");
+            return ResponseEntity.ok(response);
+
+        } catch (AuthenticationException e) {
+            // 인증 실패 시 401 Unauthorized 응답
+            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
+        }
+    }
+
+    @PostMapping("/signup")
+    public ResponseEntity<?> signup(@RequestBody UserDto userDto, HttpServletRequest request) {
+        try {
+            
+            // 1. 원본 비밀번호 저장
+            String rawPassword = userDto.getPassword();
+
+            // 2. 회원가입 처리
+            Long userId = userService.save(userDto);
+
+            // 3. 자동 로그인을 위한 인증 처리
+            Authentication authentication = authenticationManager.authenticate(
+                new UsernamePasswordAuthenticationToken(
+                    userDto.getUserId(),
+                    rawPassword
+                )
+            );
+            
+            // 4. SecurityContext에 인증 정보 저장
+            SecurityContextHolder.getContext().setAuthentication(authentication);
+
+            // 5. 세션 생성 및 저장
+            HttpSession session = request.getSession(true);  // 새 세션 생성
+            session.setAttribute("SPRING_SECURITY_CONTEXT", SecurityContextHolder.getContext()); // 시큐리티 컨텍스트를 세션에 저장
+
+            Map<String, Object> response = new HashMap<>();
+            response.put("message", "회원가입이 완료되었으며 로그인되었습니다.");
+            response.put("userId", userId);
+            return ResponseEntity.ok(response);
+            
+        } catch (DuplicatedUserException e) {
+            throw e;  // GlobalExceptionHandler로 위임
+        } catch (AuthenticationException e) {
+            return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
+                .body(new HashMap<String, String>() {{ 
+                    put("message", "회원가입은 완료되었으나 자동 로그인 처리 중 오류가 발생했습니다.");
+                }});
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/jnu/projectlab/auth/LoginRequest.java b/src/main/java/com/jnu/projectlab/auth/LoginRequest.java
new file mode 100644
index 0000000..c3507e1
--- /dev/null
+++ b/src/main/java/com/jnu/projectlab/auth/LoginRequest.java
@@ -0,0 +1,19 @@
+package com.jnu.projectlab.auth;
+
+import lombok.Getter;
+import lombok.Setter;
+import lombok.NoArgsConstructor;
+import lombok.AllArgsConstructor;
+
+/**
+ * 사용자 로그인 요청을 처리하기 위한 DTO 클래스
+ * 클라이언트로부터 로그인 시 필요한 사용자 ID와 비밀번호를 전달받습니다.
+ */
+@Getter
+@Setter
+@NoArgsConstructor
+@AllArgsConstructor
+public class LoginRequest {
+    private String userId;
+    private String password;
+}
\ No newline at end of file
diff --git a/src/main/java/com/jnu/projectlab/config/AuthController.java b/src/main/java/com/jnu/projectlab/config/AuthController.java
deleted file mode 100644
index c73433e..0000000
--- a/src/main/java/com/jnu/projectlab/config/AuthController.java
+++ /dev/null
@@ -1,46 +0,0 @@
-package com.jnu.projectlab.config;
-
-import com.jnu.projectlab.user.UserDto;
-import com.jnu.projectlab.user.UserService;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.http.HttpStatus;
-import org.springframework.http.ResponseEntity;
-import org.springframework.security.core.Authentication;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
-
-import java.util.HashMap;
-import java.util.Map;
-
-@RestController
-@RequestMapping("/auth")
-public class AuthController {
-
-    @Autowired
-    private UserService userService;
-
-    @PostMapping("/login")
-    public ResponseEntity<?> login(Authentication authentication) {
-        // 인증 정보가 이미 SecurityContextHolder에 저장됨
-        if (authentication != null && authentication.isAuthenticated()) {
-            // 현재 인증된 사용자 정보 반환
-            Map<String, Object> response = new HashMap<>();
-            response.put("message", "로그인이 성공적으로 완료되었습니다.");
-            return ResponseEntity.ok(response);
-        }
-        return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
-    }
-
-    // 회원가입 엔드포인트 추가
-    @PostMapping("/signup") // 명세에 맞게 엔드포인트 수정
-    public ResponseEntity<?> signup(@RequestBody UserDto userDto) {
-        Long userId = userService.save(userDto); // 회원가입 메서드 호출
-
-        Map<String, Object> response = new HashMap<>();
-        response.put("message", "회원가입이 완료되었으며 로그인되었습니다.");
-
-        return ResponseEntity.ok(response); // Map 객체 반환
-    }
-}
\ No newline at end of file
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index 2da8e23..d5c63d2 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -25,3 +25,13 @@ server.port=8080
 
 # Timezone Setting
 spring.jackson.time-zone=Asia/Seoul
+
+# Session Timeout (30m)
+server.servlet.session.timeout=30m
+
+# Setting cookie
+server.servlet.session.cookie.name=SESSIONID
+server.servlet.session.cookie.http-only=true
+# server.servlet.session.cookie.secure=true
+
+


Reviews:
user: login: yunsammy
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-17T10:49:05Z
user: login: yunsammy
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-17T10:52:29Z
user: login: sangkyounge
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-17T11:11:24Z
user: login: sangkyounge
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-17T11:12:44Z
user: login: sangkyounge
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-17T11:13:22Z
user: login: sangkyounge
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-17T11:13:44Z
user: login: sangkyounge
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-17T11:14:24Z
user: login: sangkyounge
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-17T11:14:40Z
user: login: yunsammy
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-17T11:16:50Z
user: login: sangkyounge
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-17T11:17:15Z
user: login: yunsammy
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-17T12:21:52Z
user: login: yunsammy
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-18T10:10:34Z
user: login: yunsammy
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-18T10:41:51Z

Review comments:
diff_hunk: @@ -25,3 +25,14 @@ server.port=8080
 
 # Timezone Setting
 spring.jackson.time-zone=Asia/Seoul
+
+
+# ?? ???? ?? (30?)
path: src/main/resources/application.properties
user: login: yunsammy
body: 주석 수정 필요
created_at: 2025-05-17T10:28:59Z
author_association: NONE
diff_hunk: @@ -25,3 +25,14 @@ server.port=8080
 
 # Timezone Setting
 spring.jackson.time-zone=Asia/Seoul
+
+
+# ?? ???? ?? (30?)
+server.servlet.session.timeout=30m
+
+# ?? ?? ??
path: src/main/resources/application.properties
user: login: yunsammy
body: 주석 수정 필요
created_at: 2025-05-17T10:29:04Z
author_association: NONE
diff_hunk: @@ -25,3 +25,14 @@ server.port=8080
 
 # Timezone Setting
 spring.jackson.time-zone=Asia/Seoul
+
+
+# ?? ???? ?? (30?)
+server.servlet.session.timeout=30m
+
+# ?? ?? ??
+server.servlet.session.cookie.name=SESSIONID
path: src/main/resources/application.properties
user: login: yunsammy
body: 쿠키 이름 변경 이유는요?
created_at: 2025-05-17T10:35:40Z
author_association: NONE
path: src/main/java/com/jnu/projectlab/config/LoginRequest.java
user: login: yunsammy
body: DTO Class가 config 폴더에 들어가는 이유 알려주시면 좋을 거 같아요
created_at: 2025-05-17T10:36:47Z
author_association: NONE
diff_hunk: @@ -0,0 +1,26 @@
+package com.jnu.projectlab.config;
+
+/**
+ * 사용자 로그인 요청을 처리하기 위한 DTO 클래스
+ * 클라이언트로부터 로그인 시 필요한 사용자 ID와 비밀번호를 전달받습니다.
+ */
+public class LoginRequest {
+    private String userId;
+    private String password;
+
+    public String getUserId() {
+        return userId;
+    }
+
+    public void setUserId(String userId) {
+        this.userId = userId;
+    }
+
+    public String getPassword() {
+        return password;
+    }
+
+    public void setPassword(String password) {
+        this.password = password;
+    }
path: src/main/java/com/jnu/projectlab/config/LoginRequest.java
user: login: yunsammy
body: Lombok의 @Data annotation 사용하면 해당 코드 간결화 할 수 있을거 같아요
created_at: 2025-05-17T10:38:51Z
author_association: NONE
diff_hunk: @@ -21,26 +22,47 @@ public class AuthController {
     @Autowired
     private UserService userService;
 
+    @Autowired
+    private AuthenticationManager authenticationManager;
+
+    /**
+     * 사용자 로그인을 처리하는 엔드포인트
+     * 
+     * @param loginRequest 로그인 요청 정보 (사용자 ID와 비밀번호 포함)
+     * @return ResponseEntity<?> 로그인 성공 시 성공 메시지, 실패 시 401 Unauthorized
+     */
     @PostMapping("/login")
-    public ResponseEntity<?> login(Authentication authentication) {
-        // 인증 정보가 이미 SecurityContextHolder에 저장됨
-        if (authentication != null && authentication.isAuthenticated()) {
-            // 현재 인증된 사용자 정보 반환
+    public ResponseEntity<?> login(@RequestBody LoginRequest loginRequest) {
+        try {
+            // 인증 매니저를 통해 사용자 인증 시도
+            Authentication authentication = authenticationManager.authenticate(
+                new UsernamePasswordAuthenticationToken(
+                    loginRequest.getUserId(),
+                    loginRequest.getPassword()
+                )
+            );
+
+            // 인증 성공 시 SecurityContext에 인증 정보 저장
+            SecurityContextHolder.getContext().setAuthentication(authentication);
+
+            // 성공 응답 생성
             Map<String, Object> response = new HashMap<>();
             response.put("message", "로그인이 성공적으로 완료되었습니다.");
             return ResponseEntity.ok(response);
+
+        } catch (AuthenticationException e) {
+            // 인증 실패 시 401 Unauthorized 응답
+            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
         }
-        return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
     }
 
-    // 회원가입 엔드포인트 추가
-    @PostMapping("/signup") // 명세에 맞게 엔드포인트 수정
+    @PostMapping("/signup")
     public ResponseEntity<?> signup(@RequestBody UserDto userDto) {
-        Long userId = userService.save(userDto); // 회원가입 메서드 호출
+        Long userId = userService.save(userDto);
 
         Map<String, Object> response = new HashMap<>();
         response.put("message", "회원가입이 완료되었으며 로그인되었습니다.");
-
-        return ResponseEntity.ok(response); // Map 객체 반환
+        response.put("userId", userId);
+        return ResponseEntity.ok(response);
path: src/main/java/com/jnu/projectlab/config/AuthController.java
user: login: yunsammy
body: signup이 정상적으로 완료된 후에도 세션 발급하도록 수정해주시면 좋을 거 같아요
created_at: 2025-05-17T10:40:30Z
author_association: NONE
path: src/main/java/com/jnu/projectlab/config/AuthController.java
user: login: yunsammy
body: 이것도 Controller인데 config에 들어가있는것 같습니다
auth directory로 옮겨주심 좋을거 같아요
created_at: 2025-05-17T10:41:45Z
author_association: NONE
diff_hunk: @@ -25,3 +25,14 @@ server.port=8080
 
 # Timezone Setting
 spring.jackson.time-zone=Asia/Seoul
+
+
+# ?? ???? ?? (30?)
+server.servlet.session.timeout=30m
+
+# ?? ?? ??
+server.servlet.session.cookie.name=SESSIONID
+server.servlet.session.cookie.http-only=true
path: src/main/resources/application.properties
user: login: yunsammy
body: http only option은 보안적인 측면에서 당연히 해야되지만
현재 개발 서버에서는 https가 적용되지 않아 이부분 주석 처리후 배포 시 활성화 해야될 것 같습니다
created_at: 2025-05-17T10:52:29Z
author_association: NONE
diff_hunk: @@ -25,3 +25,14 @@ server.port=8080
 
 # Timezone Setting
 spring.jackson.time-zone=Asia/Seoul
+
+
+# ?? ???? ?? (30?)
path: src/main/resources/application.properties
user: login: sangkyounge
body: 헉 확인했습니다

created_at: 2025-05-17T11:11:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -25,3 +25,14 @@ server.port=8080
 
 # Timezone Setting
 spring.jackson.time-zone=Asia/Seoul
+
+
+# ?? ???? ?? (30?)
+server.servlet.session.timeout=30m
+
+# ?? ?? ??
+server.servlet.session.cookie.name=SESSIONID
path: src/main/resources/application.properties
user: login: sangkyounge
body: 원래는 이름이 JSESSION인데 편하게 보기위해서 바꿨습니다!
created_at: 2025-05-17T11:12:44Z
author_association: CONTRIBUTOR
path: src/main/java/com/jnu/projectlab/config/LoginRequest.java
user: login: sangkyounge
body: 좀 급하게 작성했기도 했고  user 폴더에 넣기도 애매해서 그랬습니다 그냥 user 폴더에 넣을까요?
created_at: 2025-05-17T11:13:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,26 @@
+package com.jnu.projectlab.config;
+
+/**
+ * 사용자 로그인 요청을 처리하기 위한 DTO 클래스
+ * 클라이언트로부터 로그인 시 필요한 사용자 ID와 비밀번호를 전달받습니다.
+ */
+public class LoginRequest {
+    private String userId;
+    private String password;
+
+    public String getUserId() {
+        return userId;
+    }
+
+    public void setUserId(String userId) {
+        this.userId = userId;
+    }
+
+    public String getPassword() {
+        return password;
+    }
+
+    public void setPassword(String password) {
+        this.password = password;
+    }
path: src/main/java/com/jnu/projectlab/config/LoginRequest.java
user: login: sangkyounge
body: 아 맞아요 처음에 사용하려고 했다가 바꿨긴 했습니다 바꿔놓을게요
created_at: 2025-05-17T11:13:44Z
author_association: CONTRIBUTOR
diff_hunk: @@ -21,26 +22,47 @@ public class AuthController {
     @Autowired
     private UserService userService;
 
+    @Autowired
+    private AuthenticationManager authenticationManager;
+
+    /**
+     * 사용자 로그인을 처리하는 엔드포인트
+     * 
+     * @param loginRequest 로그인 요청 정보 (사용자 ID와 비밀번호 포함)
+     * @return ResponseEntity<?> 로그인 성공 시 성공 메시지, 실패 시 401 Unauthorized
+     */
     @PostMapping("/login")
-    public ResponseEntity<?> login(Authentication authentication) {
-        // 인증 정보가 이미 SecurityContextHolder에 저장됨
-        if (authentication != null && authentication.isAuthenticated()) {
-            // 현재 인증된 사용자 정보 반환
+    public ResponseEntity<?> login(@RequestBody LoginRequest loginRequest) {
+        try {
+            // 인증 매니저를 통해 사용자 인증 시도
+            Authentication authentication = authenticationManager.authenticate(
+                new UsernamePasswordAuthenticationToken(
+                    loginRequest.getUserId(),
+                    loginRequest.getPassword()
+                )
+            );
+
+            // 인증 성공 시 SecurityContext에 인증 정보 저장
+            SecurityContextHolder.getContext().setAuthentication(authentication);
+
+            // 성공 응답 생성
             Map<String, Object> response = new HashMap<>();
             response.put("message", "로그인이 성공적으로 완료되었습니다.");
             return ResponseEntity.ok(response);
+
+        } catch (AuthenticationException e) {
+            // 인증 실패 시 401 Unauthorized 응답
+            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
         }
-        return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
     }
 
-    // 회원가입 엔드포인트 추가
-    @PostMapping("/signup") // 명세에 맞게 엔드포인트 수정
+    @PostMapping("/signup")
     public ResponseEntity<?> signup(@RequestBody UserDto userDto) {
-        Long userId = userService.save(userDto); // 회원가입 메서드 호출
+        Long userId = userService.save(userDto);
 
         Map<String, Object> response = new HashMap<>();
         response.put("message", "회원가입이 완료되었으며 로그인되었습니다.");
-
-        return ResponseEntity.ok(response); // Map 객체 반환
+        response.put("userId", userId);
+        return ResponseEntity.ok(response);
path: src/main/java/com/jnu/projectlab/config/AuthController.java
user: login: sangkyounge
body: 아 확인했습니다 수정할게요! 
created_at: 2025-05-17T11:14:24Z
author_association: CONTRIBUTOR
path: src/main/java/com/jnu/projectlab/config/AuthController.java
user: login: sangkyounge
body: 넵
created_at: 2025-05-17T11:14:40Z
author_association: CONTRIBUTOR
path: src/main/java/com/jnu/projectlab/config/LoginRequest.java
user: login: yunsammy
body: auth 디렉터리에 넣는게 좋을 것 같습니다~
created_at: 2025-05-17T11:16:50Z
author_association: NONE
diff_hunk: @@ -25,3 +25,14 @@ server.port=8080
 
 # Timezone Setting
 spring.jackson.time-zone=Asia/Seoul
+
+
+# ?? ???? ?? (30?)
+server.servlet.session.timeout=30m
+
+# ?? ?? ??
+server.servlet.session.cookie.name=SESSIONID
path: src/main/resources/application.properties
user: login: sangkyounge
body: 이거 다시확인해보니 한글이 깨져서 그렇네요 영어로 수정해놓을게요
created_at: 2025-05-17T11:17:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -25,3 +25,14 @@ server.port=8080
 
 # Timezone Setting
 spring.jackson.time-zone=Asia/Seoul
+
+
+# ?? ???? ?? (30?)
+server.servlet.session.timeout=30m
+
+# ?? ?? ??
+server.servlet.session.cookie.name=SESSIONID
+server.servlet.session.cookie.http-only=true
path: src/main/resources/application.properties
user: login: yunsammy
body: @sangkyounge 
반영 안된 것 같습니다~
created_at: 2025-05-17T12:21:52Z
author_association: NONE
diff_hunk: @@ -25,3 +25,14 @@ server.port=8080
 
 # Timezone Setting
 spring.jackson.time-zone=Asia/Seoul
+
+
+# ?? ???? ?? (30?)
+server.servlet.session.timeout=30m
+
+# ?? ?? ??
+server.servlet.session.cookie.name=SESSIONID
+server.servlet.session.cookie.http-only=true
path: src/main/resources/application.properties
user: login: yunsammy
body: @sangkyounge 
아 제가 잘못 말씀드렸네요
http only가 아니라 secure 옵션입니다.
created_at: 2025-05-18T10:10:34Z
author_association: NONE
diff_hunk: @@ -25,3 +25,14 @@ server.port=8080
 
 # Timezone Setting
 spring.jackson.time-zone=Asia/Seoul
+
+
+# ?? ???? ?? (30?)
+server.servlet.session.timeout=30m
+
+# ?? ?? ??
+server.servlet.session.cookie.name=SESSIONID
+server.servlet.session.cookie.http-only=true
path: src/main/resources/application.properties
user: login: yunsammy
body: 제가 수정하였습니다~
created_at: 2025-05-18T10:41:50Z
author_association: NONE

PR comments:
user: login: yunsammy
created_at: 2025-05-17T10:49:58Z
body: @sangkyounge 
리뷰 확인 부탁드립니다~
그리고 명세서도 변경된 사항에 맞게 수정 부탁드립니다
author_association: NONE
user: login: yunsammy
created_at: 2025-05-17T12:22:27Z
body: @sangkyounge 
login, singup 후 session을 쿠키에 넣어 반환하는 부분은 security filter chain 부분에서 진행되는 건가요?
author_association: NONE
user: login: sangkyounge
created_at: 2025-05-17T12:25:02Z
body: > @sangkyounge login, singup 후 session을 쿠키에 넣어 반환하는 부분은 security filter chain 부분에서 진행되는 건가요?

넵 맞습니다!
author_association: CONTRIBUTOR