feat: singin using phone number backend
This commit is contained in:
parent
1f92121491
commit
a74a6a3741
@ -36,10 +36,10 @@ public class AuthController {
|
||||
}
|
||||
|
||||
@PostMapping("/login")
|
||||
@Operation(summary = "User login", description = "Authenticate user with email or username and return JWT token")
|
||||
@Operation(summary = "User login", description = "Authenticate user with phone number or username and return JWT token")
|
||||
public ResponseEntity<LoginResponse> login(@Valid @RequestBody LoginRequest loginRequest) {
|
||||
try {
|
||||
String token = authService.authenticate(loginRequest.getEmailOrUsername(), loginRequest.getPassword());
|
||||
String token = authService.authenticate(loginRequest.getPhoneNumberOrUsername(), loginRequest.getPassword());
|
||||
return ResponseEntity.ok(new LoginResponse(token));
|
||||
} catch (RuntimeException e) {
|
||||
return ResponseEntity.badRequest().build();
|
||||
@ -84,7 +84,7 @@ public class AuthController {
|
||||
// Create user data without password
|
||||
Map<String, Object> userData = Map.of(
|
||||
"id", user.getId(),
|
||||
"email", user.getEmail(),
|
||||
"phoneNumber", user.getPhoneNumber(),
|
||||
"username", user.getUsername() != null ? user.getUsername() : "",
|
||||
"displayName", user.getDisplayName(),
|
||||
"avatar", user.getAvatar() != null ? user.getAvatar() : "",
|
||||
@ -111,7 +111,7 @@ public class AuthController {
|
||||
@Operation(summary = "Create new user (Admin only)", description = "Creates a new user - requires admin privileges")
|
||||
public ResponseEntity<?> createUser(@Valid @RequestBody CreateUserRequest request) {
|
||||
try {
|
||||
User user = userService.createUser(request.getEmail(), request.getPassword(), request.getDisplayName());
|
||||
User user = userService.createUser(request.getPhoneNumber(), request.getPassword(), request.getDisplayName());
|
||||
return ResponseEntity.ok(user);
|
||||
} catch (RuntimeException e) {
|
||||
return ResponseEntity.badRequest().body(Map.of("error", e.getMessage()));
|
||||
|
||||
@ -51,8 +51,8 @@ public class InvitationController {
|
||||
}
|
||||
|
||||
try {
|
||||
String userEmail = authentication.getName();
|
||||
InvitationResponse response = invitationService.createInvitation(request, userEmail);
|
||||
String userPhoneNumber = authentication.getName();
|
||||
InvitationResponse response = invitationService.createInvitation(request, userPhoneNumber);
|
||||
return ResponseEntity.ok(ApiResponse.success(response));
|
||||
} catch (RuntimeException e) {
|
||||
String message;
|
||||
@ -87,8 +87,8 @@ public class InvitationController {
|
||||
@Operation(summary = "Get all categorized invitations", description = "Get invitations in three categories: created, accepted, and available")
|
||||
public ResponseEntity<ApiResponse<CategorizedInvitationsResponse>> getAllCategorizedInvitations(Authentication authentication) {
|
||||
try {
|
||||
String userEmail = authentication.getName();
|
||||
CategorizedInvitationsResponse response = invitationService.getAllCategorizedInvitations(userEmail);
|
||||
String userPhoneNumber = authentication.getName();
|
||||
CategorizedInvitationsResponse response = invitationService.getAllCategorizedInvitations(userPhoneNumber);
|
||||
return ResponseEntity.ok(ApiResponse.success(response));
|
||||
} catch (RuntimeException e) {
|
||||
return ResponseEntity.badRequest().body(ApiResponse.error(e.getMessage()));
|
||||
@ -104,8 +104,8 @@ public class InvitationController {
|
||||
Authentication authentication) {
|
||||
|
||||
try {
|
||||
String userEmail = authentication.getName();
|
||||
invitationService.acceptInvitation(request.getId(), userEmail);
|
||||
String userPhoneNumber = authentication.getName();
|
||||
invitationService.acceptInvitation(request.getId(), userPhoneNumber);
|
||||
return ResponseEntity.ok(new ApiResponse<>(true));
|
||||
} catch (RuntimeException e) {
|
||||
return ResponseEntity.badRequest().body(ApiResponse.error(e.getMessage()));
|
||||
@ -121,8 +121,8 @@ public class InvitationController {
|
||||
Authentication authentication) {
|
||||
|
||||
try {
|
||||
String userEmail = authentication.getName();
|
||||
invitationService.cancelInvitation(request.getId(), userEmail);
|
||||
String userPhoneNumber = authentication.getName();
|
||||
invitationService.cancelInvitation(request.getId(), userPhoneNumber);
|
||||
return ResponseEntity.ok(new ApiResponse<>(true));
|
||||
} catch (RuntimeException e) {
|
||||
return ResponseEntity.badRequest().body(ApiResponse.error(e.getMessage()));
|
||||
|
||||
@ -89,7 +89,7 @@ public class PostController {
|
||||
Long targetUserId;
|
||||
if (id == null) {
|
||||
// Use authenticated user's ID when no id is provided
|
||||
String userEmail = authentication.getName();
|
||||
String userPhoneNumber = authentication.getName();
|
||||
targetUserId = userService.getCurrentUser().getId();
|
||||
} else {
|
||||
if (id <= 0) {
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
package online.wesal.wesal.dto;
|
||||
|
||||
import jakarta.validation.constraints.Email;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
|
||||
public class CreateUserRequest {
|
||||
|
||||
@Email
|
||||
@NotBlank
|
||||
private String email;
|
||||
@Pattern(regexp = "^\\+[1-9]\\d{1,14}$", message = "Phone number must be in international format starting with + and country code")
|
||||
private String phoneNumber;
|
||||
|
||||
@NotBlank
|
||||
@Size(min = 6)
|
||||
@ -19,18 +19,18 @@ public class CreateUserRequest {
|
||||
|
||||
public CreateUserRequest() {}
|
||||
|
||||
public CreateUserRequest(String email, String password, String displayName) {
|
||||
this.email = email;
|
||||
public CreateUserRequest(String phoneNumber, String password, String displayName) {
|
||||
this.phoneNumber = phoneNumber;
|
||||
this.password = password;
|
||||
this.displayName = displayName;
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return email;
|
||||
public String getPhoneNumber() {
|
||||
return phoneNumber;
|
||||
}
|
||||
|
||||
public void setEmail(String email) {
|
||||
this.email = email;
|
||||
public void setPhoneNumber(String phoneNumber) {
|
||||
this.phoneNumber = phoneNumber;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
|
||||
@ -5,24 +5,24 @@ import jakarta.validation.constraints.NotBlank;
|
||||
public class LoginRequest {
|
||||
|
||||
@NotBlank
|
||||
private String emailOrUsername;
|
||||
private String phoneNumberOrUsername;
|
||||
|
||||
@NotBlank
|
||||
private String password;
|
||||
|
||||
public LoginRequest() {}
|
||||
|
||||
public LoginRequest(String emailOrUsername, String password) {
|
||||
this.emailOrUsername = emailOrUsername;
|
||||
public LoginRequest(String phoneNumberOrUsername, String password) {
|
||||
this.phoneNumberOrUsername = phoneNumberOrUsername;
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public String getEmailOrUsername() {
|
||||
return emailOrUsername;
|
||||
public String getPhoneNumberOrUsername() {
|
||||
return phoneNumberOrUsername;
|
||||
}
|
||||
|
||||
public void setEmailOrUsername(String emailOrUsername) {
|
||||
this.emailOrUsername = emailOrUsername;
|
||||
public void setPhoneNumberOrUsername(String phoneNumberOrUsername) {
|
||||
this.phoneNumberOrUsername = phoneNumberOrUsername;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
package online.wesal.wesal.entity;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import jakarta.validation.constraints.Email;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
@ -19,9 +19,9 @@ public class User {
|
||||
private Long id;
|
||||
|
||||
@Column(unique = true, nullable = false)
|
||||
@Email
|
||||
@NotBlank
|
||||
private String email;
|
||||
@Pattern(regexp = "^\\+[1-9]\\d{1,14}$", message = "Phone number must be in international format starting with + and country code")
|
||||
private String phoneNumber;
|
||||
|
||||
@Column(unique = true)
|
||||
private String username;
|
||||
@ -50,8 +50,8 @@ public class User {
|
||||
|
||||
public User() {}
|
||||
|
||||
public User(String email, String password, String displayName) {
|
||||
this.email = email;
|
||||
public User(String phoneNumber, String password, String displayName) {
|
||||
this.phoneNumber = phoneNumber;
|
||||
this.password = password;
|
||||
this.displayName = displayName;
|
||||
}
|
||||
@ -64,12 +64,12 @@ public class User {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return email;
|
||||
public String getPhoneNumber() {
|
||||
return phoneNumber;
|
||||
}
|
||||
|
||||
public void setEmail(String email) {
|
||||
this.email = email;
|
||||
public void setPhoneNumber(String phoneNumber) {
|
||||
this.phoneNumber = phoneNumber;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
|
||||
@ -11,9 +11,9 @@ import java.util.Optional;
|
||||
|
||||
@Repository
|
||||
public interface UserRepository extends JpaRepository<User, Long> {
|
||||
Optional<User> findByEmail(String email);
|
||||
Optional<User> findByPhoneNumber(String phoneNumber);
|
||||
Optional<User> findByUsername(String username);
|
||||
boolean existsByEmail(String email);
|
||||
boolean existsByPhoneNumber(String phoneNumber);
|
||||
boolean existsByUsername(String username);
|
||||
|
||||
@Query("SELECT u FROM User u WHERE u.subscriptions LIKE %:subscription% AND u.fcmToken IS NOT NULL AND u.fcmToken != ''")
|
||||
|
||||
@ -21,23 +21,23 @@ public class AuthService {
|
||||
@Autowired
|
||||
private JwtUtil jwtUtil;
|
||||
|
||||
public String authenticate(String emailOrUsername, String password) {
|
||||
Optional<User> userOpt = userRepository.findByEmail(emailOrUsername);
|
||||
public String authenticate(String phoneNumberOrUsername, String password) {
|
||||
Optional<User> userOpt = userRepository.findByPhoneNumber(phoneNumberOrUsername);
|
||||
|
||||
if (userOpt.isEmpty()) {
|
||||
userOpt = userRepository.findByUsername(emailOrUsername);
|
||||
userOpt = userRepository.findByUsername(phoneNumberOrUsername);
|
||||
}
|
||||
|
||||
if (userOpt.isPresent()) {
|
||||
User user = userOpt.get();
|
||||
System.out.println("Authenticating user: " + user.getEmail());
|
||||
System.out.println("Authenticating user: " + user.getPhoneNumber());
|
||||
if (passwordEncoder.matches(password, user.getPassword())) {
|
||||
System.out.println("Password matches!");
|
||||
if (!user.isActivated()) {
|
||||
user.setActivated(true);
|
||||
userRepository.save(user);
|
||||
}
|
||||
return jwtUtil.generateToken(user.getEmail());
|
||||
return jwtUtil.generateToken(user.getPhoneNumber());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -42,8 +42,8 @@ public class InvitationService {
|
||||
private SubscriptionNotificationService subscriptionNotificationService;
|
||||
|
||||
@Transactional
|
||||
public InvitationResponse createInvitation(CreateInvitationRequest request, String userEmail) {
|
||||
User creator = userRepository.findByEmail(userEmail)
|
||||
public InvitationResponse createInvitation(CreateInvitationRequest request, String userPhoneNumber) {
|
||||
User creator = userRepository.findByPhoneNumber(userPhoneNumber)
|
||||
.orElseThrow(() -> new RuntimeException("User not found"));
|
||||
|
||||
Tag tag = tagRepository.findById(request.getTagId())
|
||||
@ -85,8 +85,8 @@ public class InvitationService {
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public CategorizedInvitationsResponse getAllCategorizedInvitations(String userEmail) {
|
||||
User user = userRepository.findByEmail(userEmail)
|
||||
public CategorizedInvitationsResponse getAllCategorizedInvitations(String userPhoneNumber) {
|
||||
User user = userRepository.findByPhoneNumber(userPhoneNumber)
|
||||
.orElseThrow(() -> new RuntimeException("Authentication error. Please log in again"));
|
||||
|
||||
List<InvitationResponse> created = invitationRepository.findByCreatorIdOrderByCreationDateDesc(user.getId())
|
||||
@ -110,8 +110,8 @@ public class InvitationService {
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void acceptInvitation(Long invitationId, String userEmail) {
|
||||
User user = userRepository.findByEmail(userEmail)
|
||||
public void acceptInvitation(Long invitationId, String userPhoneNumber) {
|
||||
User user = userRepository.findByPhoneNumber(userPhoneNumber)
|
||||
.orElseThrow(() -> new RuntimeException("Authentication error. Please log in again"));
|
||||
|
||||
Invitation invitation = invitationRepository.findById(invitationId)
|
||||
@ -171,8 +171,8 @@ public class InvitationService {
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void cancelInvitation(Long invitationId, String userEmail) {
|
||||
User user = userRepository.findByEmail(userEmail)
|
||||
public void cancelInvitation(Long invitationId, String userPhoneNumber) {
|
||||
User user = userRepository.findByPhoneNumber(userPhoneNumber)
|
||||
.orElseThrow(() -> new RuntimeException("Authentication error. Please log in again"));
|
||||
|
||||
Invitation invitation = invitationRepository.findById(invitationId)
|
||||
|
||||
@ -20,15 +20,15 @@ public class UserDetailsServiceImpl implements UserDetailsService {
|
||||
private UserRepository userRepository;
|
||||
|
||||
@Override
|
||||
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
|
||||
User user = userRepository.findByEmail(email)
|
||||
.orElseThrow(() -> new UsernameNotFoundException("User not found: " + email));
|
||||
public UserDetails loadUserByUsername(String phoneNumber) throws UsernameNotFoundException {
|
||||
User user = userRepository.findByPhoneNumber(phoneNumber)
|
||||
.orElseThrow(() -> new UsernameNotFoundException("User not found: " + phoneNumber));
|
||||
|
||||
Collection<GrantedAuthority> authorities = new ArrayList<>();
|
||||
authorities.add(new SimpleGrantedAuthority("ROLE_" + user.getRole()));
|
||||
|
||||
return new org.springframework.security.core.userdetails.User(
|
||||
user.getEmail(),
|
||||
user.getPhoneNumber(),
|
||||
user.getPassword(),
|
||||
authorities
|
||||
);
|
||||
|
||||
@ -20,8 +20,8 @@ public class UserService {
|
||||
|
||||
public User getCurrentUser() {
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
String email = authentication.getName();
|
||||
return userRepository.findByEmail(email)
|
||||
String phoneNumber = authentication.getName();
|
||||
return userRepository.findByPhoneNumber(phoneNumber)
|
||||
.orElseThrow(() -> new RuntimeException("User not found"));
|
||||
}
|
||||
|
||||
@ -44,16 +44,16 @@ public class UserService {
|
||||
return "ADMIN".equals(user.getRole());
|
||||
}
|
||||
|
||||
public User createUser(String email, String password, String displayName) {
|
||||
public User createUser(String phoneNumber, String password, String displayName) {
|
||||
if (!isCurrentUserAdmin()) {
|
||||
throw new RuntimeException("Access denied: Admin privileges required");
|
||||
}
|
||||
|
||||
if (userRepository.existsByEmail(email)) {
|
||||
throw new RuntimeException("User with this email already exists");
|
||||
if (userRepository.existsByPhoneNumber(phoneNumber)) {
|
||||
throw new RuntimeException("User with this phone number already exists");
|
||||
}
|
||||
|
||||
User user = new User(email, passwordEncoder.encode(password), displayName);
|
||||
User user = new User(phoneNumber, passwordEncoder.encode(password), displayName);
|
||||
return userRepository.save(user);
|
||||
}
|
||||
|
||||
|
||||
@ -3,6 +3,7 @@ import 'package:flutter/foundation.dart';
|
||||
import 'dart:convert';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:googleapis_auth/auth_io.dart';
|
||||
import 'package:flutter/services.dart' show rootBundle;
|
||||
|
||||
class NotificationService {
|
||||
static final NotificationService _instance = NotificationService._internal();
|
||||
@ -17,22 +18,8 @@ class NotificationService {
|
||||
// Firebase project configuration
|
||||
static const String _projectId = 'wesalapp-bc676';
|
||||
|
||||
// Service account credentials (JSON string)
|
||||
static const String _serviceAccountJson = '''
|
||||
{
|
||||
"type": "service_account",
|
||||
"project_id": "wesalapp-bc676",
|
||||
"private_key_id": "90f1ac73e8f8b59e5ad7b0caed638ffa57675d39",
|
||||
"private_key": "-----BEGIN PRIVATE KEY-----\\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCe/p4CkGeDft2F\\nLMDNyjbU0g1mLQ4bhaqy2WOHr8BX7kHt/WlpB3WeaSUItW2cXFtXhI66mW7Ejlg7\\n/Los4YLpVXner4P5Vj7sOVrhD1Jt9qmdsEjfsTaKs0t06tfUE9PdFqBl6F5HG9B1\\nkwdV2mQfiurpTb+zUXLpfvMbGT1ny7gJSXhYpRaC6UJUy7DNsxeChviXy1tPGr/r\\nmIaENt0KAZzRSSCp0bzN+gKoAm6qyOGYJ9++HlGXTDYmkGPkdc0lbSWjA2/+K7zR\\nWQNy4oOXCstdHL4lMp87UT8lL1ZnntROELyTBUslmYywxXtZinkOgBrWeUifqLW4\\nbcHy6jJ5AgMBAAECggEACSBwjZEggAneSXDCOI3tC9Zq8nyPnMDVhaK49eb+0Y1Z\\nt4GedWr6M3exqohPnHQowiNX1hpMo3fQVNEzFrRzQVWow0Gr/7oVrpW0Q8sPXkSU\\ng/rElCKmENwt7q40aXYh6UUNPAxUrRxJoRYpi6IXsT/WMEJISNDaGdExv1J5leWi\\no8Op2AhREV/ukFpTnNfzfWKjXN+i3psNCYqZAdAh+a4ZJH0vNpiaCq6uVFw7HzdR\\nF2mMha+MYtp2VupzDJ8DkL4ExQl1KCOCllahzqVrhqmEhhhTfDxPOj5q24Hnfn1p\\npzR+fC8Ex0dGB/j+9jKjQyilo/LzEJdrPxt/9QUdiQKBgQDQ2L7sQsEIihVUq3lW\\n3Od2GNjnloTo24rNLl8hxwgl9EA2VnG50gZPGAkcUA2eeA23T6C6gPbm0QBsfqSP\\nPNTbd6UYF508EE7PMaScFoJMcKDin8x4q5tfVjgao2r8LCOUXfU1ktreQR3bIMKk\\nsgsgBazfBT84ioxvDwoD+4EJqwKBgQDC5HEfouCTxvKwzmpxZ+kkHKPO5QjyxnOH\\nLnt/7jx5t7T1nWNUnusYj+uowaqKAmLz7kBhCbRGADdKuBAr0hY/aEOG3zhTH35K\\nc+8wJ3yDFkh8BhFsOYCxopIPAjEGxw5cnM4+r8QDqy61j4hsR9kSr40WwhRuSxu+\\nHqe38Vl4awKBgBYFJGxIxY2e8YzR36NW+1iqWgRhDHZ433Ou1fz7vVIzJKoWBzuu\\nd1fTkvJXRnhU9C1Fyg6gFmhT1RWbbMJliZPyU4fsxXlVxtl1xINopChnH6+FZcu7\\nXFB7CMNWQ6t/A+la1sXlTApvFzTJiXxQAXhI4OdK6FWP1irHjSjKVdqtAoGAcLQA\\ngyYCrxKux/YmcfyAQ2TYic3DNfnzVypXOuz/RfgpipwAlC/ujl60Dfwo7fRhWuTd\\nkAA3ov9++hOlLmIogXR/EGDHxrIAq3eNy5AaHghl1GsB6k76kD8OLeW7ikrUkFQR\\npQip1uFIerBNWSjXbEne0llbzUhb+77oiKPmdI8CgYBRBIUC4d/T3cgmF9uRqlpL\\nSsa8IxqohU1huAucf5UqNP9OXZ4TtZhM0PbR5SNHcGBXAl+XowCtUeCE9LlWWzpg\\ne/xTu4Mu1hwnRZ8ybujAyTPnN8KEfK8HDjORZnxzzdyPkO4BN+KOH6WZyKhKDTuR\\n6KCch1ooA8YlV43vchpKXg==\\n-----END PRIVATE KEY-----\\n",
|
||||
"client_email": "firebase-adminsdk-fbsvc@wesalapp-bc676.iam.gserviceaccount.com",
|
||||
"client_id": "112586481303824467416",
|
||||
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
|
||||
"token_uri": "https://oauth2.googleapis.com/token",
|
||||
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
|
||||
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-fbsvc%40wesalapp-bc676.iam.gserviceaccount.com",
|
||||
"universe_domain": "googleapis.com"
|
||||
}
|
||||
''';
|
||||
// Service account credentials loaded from file
|
||||
static String? _serviceAccountJson;
|
||||
|
||||
static const List<String> topics = [
|
||||
'all',
|
||||
@ -257,10 +244,30 @@ class NotificationService {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _loadServiceAccountCredentials() async {
|
||||
if (_serviceAccountJson != null) return;
|
||||
|
||||
try {
|
||||
_serviceAccountJson = await rootBundle.loadString(
|
||||
'firebase-service-account.json',
|
||||
);
|
||||
} catch (e) {
|
||||
print('Error loading service account credentials: $e');
|
||||
_serviceAccountJson = null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<String?> _getAccessToken() async {
|
||||
try {
|
||||
await _loadServiceAccountCredentials();
|
||||
|
||||
if (_serviceAccountJson == null) {
|
||||
print('Service account credentials not loaded');
|
||||
return null;
|
||||
}
|
||||
|
||||
final credentials = ServiceAccountCredentials.fromJson(
|
||||
_serviceAccountJson,
|
||||
_serviceAccountJson!,
|
||||
);
|
||||
final client = await clientViaServiceAccount(credentials, [
|
||||
'https://www.googleapis.com/auth/firebase.messaging',
|
||||
|
||||
Loading…
Reference in New Issue
Block a user